use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
use std::time::Duration;
use image;
use lazy_static::lazy_static;
use libc;
use xcb::ffi::base::{xcb_request_check, XCB_CURRENT_TIME};
use xcb::ffi::xproto::{
xcb_atom_t, xcb_change_property, xcb_get_property, xcb_get_property_reply,
xcb_get_property_reply_t, xcb_get_property_value, xcb_get_property_value_length,
xcb_get_selection_owner_reply, xcb_property_notify_event_t, xcb_selection_clear_event_t,
xcb_selection_notify_event_t, xcb_selection_request_event_t, xcb_send_event,
xcb_set_selection_owner_checked, XCB_ATOM_NONE, XCB_EVENT_MASK_NO_EVENT,
XCB_PROPERTY_NEW_VALUE, XCB_PROP_MODE_REPLACE, XCB_SELECTION_NOTIFY,
};
use xcb::{
self,
xproto::{self, get_selection_owner},
};
use super::common::{Error, ImageData};
const ATOM: usize = 0;
const INCR: usize = 1;
const TARGETS: usize = 2;
const CLIPBOARD: usize = 3;
const MIME_IMAGE_PNG: usize = 4;
const ATOM_PAIR: usize = 5;
const SAVE_TARGETS: usize = 6;
const MULTIPLE: usize = 7;
const CLIPBOARD_MANAGER: usize = 8;
static COMMON_ATOM_NAMES: [&'static str; 9] = [
"ATOM",
"INCR",
"TARGETS",
"CLIPBOARD",
"image/png",
"ATOM_PAIR",
"SAVE_TARGETS",
"MULTIPLE",
"CLIPBOARD_MANAGER",
];
type BufferPtr = Option<Arc<Mutex<Vec<u8>>>>;
type Atoms = Vec<xcb::xproto::Atom>;
type NotifyCallback = Option<Arc<dyn (Fn(&BufferPtr) -> bool) + Send + Sync + 'static>>;
lazy_static! {
static ref LOCKED_OBJECTS: Arc<Mutex<Option<LockedObjects>>> = Arc::new(Mutex::new(None));
static ref CONDVAR: Condvar = Condvar::new();
}
struct LockedObjects {
shared: SharedState,
manager: Manager,
}
impl LockedObjects {
fn new() -> Result<LockedObjects, Error> {
let connection = xcb::Connection::connect(None).unwrap().0;
match Manager::new(&connection) {
Ok(manager) => {
Ok(LockedObjects {
shared: SharedState {
conn: Some(Arc::new(connection)),
atoms: Default::default(),
common_atoms: Default::default(),
text_atoms: Default::default(),
},
manager,
})
}
Err(e) => Err(e),
}
}
}
struct SharedState {
conn: Option<Arc<xcb::Connection>>,
atoms: BTreeMap<String, xcb::xproto::Atom>,
common_atoms: Atoms,
text_atoms: Atoms,
}
unsafe impl Send for SharedState {}
impl SharedState {
fn get_atom_by_id(&mut self, id: usize) -> xproto::Atom {
if self.common_atoms.is_empty() {
self.common_atoms = self.get_atoms(&COMMON_ATOM_NAMES);
}
self.common_atoms[id]
}
fn get_atoms(&mut self, names: &[&'static str]) -> Atoms {
let mut results = vec![0; names.len()];
let mut cookies = HashMap::with_capacity(names.len());
for (res, name) in results.iter_mut().zip(names) {
if let Some(atom) = self.atoms.get(*name) {
*res = *atom;
} else {
cookies
.insert(*name, xproto::intern_atom(self.conn.as_ref().unwrap(), false, name));
}
}
for (res, name) in results.iter_mut().zip(names.iter()) {
if *res == 0 {
let reply = unsafe {
xcb::ffi::xproto::xcb_intern_atom_reply(
self.conn.as_ref().unwrap().get_raw_conn(),
cookies.get(name).unwrap().cookie,
std::ptr::null_mut(),
)
};
if reply != std::ptr::null_mut() {
unsafe {
*res = (*reply).atom;
self.atoms.insert((*name).into(), *res);
libc::free(reply as *mut _);
}
}
}
}
results
}
fn get_text_format_atoms(&mut self) -> &Atoms {
if self.text_atoms.is_empty() {
const NAMES: [&'static str; 6] = [
"UTF8_STRING",
"text/plain;charset=utf-8",
"text/plain;charset=UTF-8",
"STRING",
"TEXT",
"text/plain",
];
self.text_atoms = self.get_atoms(&NAMES);
}
&self.text_atoms
}
}
struct Manager {
window: xcb::xproto::Window,
thread_handle: Option<std::thread::JoinHandle<()>>,
callback: NotifyCallback,
callback_result: bool,
data: BTreeMap<xcb::xproto::Atom, BufferPtr>,
image: super::common::ImageData<'static>,
incr_process: bool,
incr_received: bool,
target_atom: xcb::xproto::Atom,
reply_data: BufferPtr,
reply_offset: usize,
}
impl Manager {
fn new(connection: &xcb::Connection) -> Result<Self, Error> {
use xcb::ffi::xproto::{
XCB_CW_EVENT_MASK, XCB_EVENT_MASK_PROPERTY_CHANGE, XCB_EVENT_MASK_STRUCTURE_NOTIFY,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
};
let setup = connection.get_setup();
if std::ptr::null() == setup.ptr {
return Err(Error::Unknown {
description: "Could not get setup for connection".into(),
});
}
let screen = setup.roots().data;
if std::ptr::null() == screen {
return Err(Error::Unknown { description: "Could not get screen from setup".into() });
}
let event_mask =
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY;
let window = connection.generate_id();
unsafe {
xcb::ffi::xproto::xcb_create_window(
connection.get_raw_conn(),
0,
window,
(*screen).root,
0,
0,
1,
1,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT as _,
(*screen).root_visual,
XCB_CW_EVENT_MASK,
&event_mask,
);
}
let thread_handle = std::thread::spawn(process_x11_events);
Ok(Manager {
window,
thread_handle: Some(thread_handle),
callback: None,
callback_result: false,
data: Default::default(),
image: super::common::ImageData {
width: 0,
height: 0,
bytes: std::borrow::Cow::from(vec![]),
},
incr_process: false,
incr_received: false,
target_atom: 0,
reply_data: Default::default(),
reply_offset: 0,
})
}
fn set_x11_selection_owner(&self, shared: &mut SharedState) -> bool {
let clipboard_atom = shared.get_atom_by_id(CLIPBOARD);
let cookie = unsafe {
xcb_set_selection_owner_checked(
shared.conn.as_ref().unwrap().get_raw_conn(),
self.window,
clipboard_atom,
XCB_CURRENT_TIME,
)
};
let err =
unsafe { xcb_request_check(shared.conn.as_ref().unwrap().get_raw_conn(), cookie) };
if err != std::ptr::null_mut() {
unsafe {
libc::free(err as *mut _);
}
return false;
}
true
}
fn set_image(&mut self, shared: &mut SharedState, image: ImageData) -> Result<(), Error> {
if !self.set_x11_selection_owner(shared) {
return Err(Error::Unknown {
description: "Failed to set x11 selection owner.".into(),
});
}
self.image.width = image.width;
self.image.height = image.height;
self.image.bytes = image.bytes.into_owned().into();
self.data.insert(shared.get_atom_by_id(MIME_IMAGE_PNG), None);
Ok(())
}
fn set_text(&mut self, shared: &mut SharedState, bytes: Vec<u8>) -> Result<(), Error> {
if !self.set_x11_selection_owner(shared) {
return Err(Error::Unknown {
description: "Could not take ownership of the x11 selection".into(),
});
}
let atoms = shared.get_text_format_atoms();
if atoms.is_empty() {
return Err(Error::Unknown { description:
"Couldn't get the atoms that identify supported text formats for the x11 clipboard"
.into(),
});
}
let arc_data = Arc::new(Mutex::new(bytes));
for atom in atoms {
self.data.insert(*atom, Some(arc_data.clone()));
}
Ok(())
}
fn clear_data(&mut self) {
self.data.clear();
self.image.width = 0;
self.image.height = 0;
self.image.bytes = Vec::new().into();
}
fn set_requestor_property_with_clipboard_content(
&mut self,
shared: &mut SharedState,
requestor: xproto::Window,
property: xproto::Atom,
target: xproto::Atom,
) -> bool {
let item = {
if let Some(item) = self.data.get_mut(&target) {
item
} else {
return false;
}
};
if item.is_none() {
encode_data_on_demand(shared, &mut self.image, target, item);
if item.is_none() {
return false;
}
}
let item = item.as_ref().unwrap().lock().unwrap();
unsafe {
xcb_change_property(
shared.conn.as_ref().unwrap().get_raw_conn(),
XCB_PROP_MODE_REPLACE as u8,
requestor,
property,
target,
8,
item.len() as u32,
item.as_ptr() as *const _,
)
};
true
}
fn copy_reply_data(&mut self, reply: *mut xcb_get_property_reply_t) {
let src = unsafe { xcb_get_property_value(reply) } as *const u8;
let n = unsafe { xcb_get_property_value_length(reply) } as usize;
let req = self.reply_offset + n;
match &mut self.reply_data {
None => {
self.reply_offset = 0;
self.reply_data = Some(Arc::new(Mutex::new(vec![0u8; req])));
}
Some(reply_data) => {
let mut reply_data = reply_data.lock().unwrap();
if req > reply_data.len() {
reply_data.resize(req, 0u8);
}
}
}
let src_slice = unsafe { std::slice::from_raw_parts(src, n) };
let mut reply_data_locked = self.reply_data.as_mut().unwrap().lock().unwrap();
reply_data_locked[self.reply_offset..req].copy_from_slice(src_slice);
self.reply_offset += n;
}
fn call_callback(&mut self, _reply: *mut xcb_get_property_reply_t) {
self.callback_result = false;
if let Some(callback) = &self.callback {
self.callback_result = callback(&self.reply_data);
}
CONDVAR.notify_one();
self.reply_data = None;
}
fn destruct() {
let join_handle;
{
let mut guard = LOCKED_OBJECTS.lock().unwrap();
if guard.is_none() {
return;
}
macro_rules! manager {
() => {
guard.as_mut().unwrap().manager
};
}
macro_rules! shared {
() => {
guard.as_mut().unwrap().shared
};
}
if !manager!().data.is_empty()
&& manager!().window != 0
&& manager!().window == get_x11_selection_owner(&mut shared!())
{
let mut x11_clipboard_manager = 0;
{
let clipboard_manager_atom = shared!().get_atom_by_id(CLIPBOARD_MANAGER);
let conn: &xcb::Connection = shared!().conn.as_ref().unwrap();
let cookie = { get_selection_owner(conn, clipboard_manager_atom) };
let reply = unsafe {
xcb::ffi::xproto::xcb_get_selection_owner_reply(
conn.get_raw_conn(),
cookie.cookie,
std::ptr::null_mut(),
)
};
if reply != std::ptr::null_mut() {
unsafe {
x11_clipboard_manager = (*reply).owner;
libc::free(reply as *mut _);
}
}
}
if x11_clipboard_manager != 0 {
let atoms = vec![shared!().get_atom_by_id(SAVE_TARGETS)];
let selection = shared!().get_atom_by_id(CLIPBOARD_MANAGER);
guard = get_data_from_selection_owner(
guard,
&atoms,
Some(Arc::new(|_| true)),
selection,
)
.1;
}
}
if manager!().window != 0 {
unsafe {
xcb::ffi::xproto::xcb_destroy_window(
shared!().conn.as_ref().unwrap().get_raw_conn(),
manager!().window,
)
};
shared!().conn.as_ref().unwrap().flush();
manager!().window = 0;
}
join_handle = manager!().thread_handle.take();
}
if let Some(handle) = join_handle {
handle.join().ok();
}
}
}
fn process_x11_events() {
use xcb::ffi::xproto::{
XCB_DESTROY_NOTIFY, XCB_PROPERTY_NOTIFY, XCB_SELECTION_CLEAR, XCB_SELECTION_REQUEST,
};
let connection = {
let lo = LOCKED_OBJECTS.lock().unwrap();
lo.as_ref().unwrap().shared.conn.clone()
};
let mut stop = false;
while !stop {
let event = {
std::thread::sleep(Duration::from_millis(5));
let maybe_event = connection.as_ref().unwrap().poll_for_event();
if connection.as_ref().unwrap().has_error().is_err() {
break;
}
if let Some(e) = maybe_event {
e
} else {
continue;
}
};
if event.ptr == std::ptr::null_mut() {
break;
}
let resp_type = unsafe { (*event.ptr).response_type & !0x80 };
match resp_type {
XCB_DESTROY_NOTIFY => {
stop = true;
}
XCB_SELECTION_CLEAR => {
handle_selection_clear_event(event.ptr as *mut xcb_selection_clear_event_t);
}
XCB_SELECTION_REQUEST => {
handle_selection_request_event(event.ptr as *mut xcb_selection_request_event_t);
}
XCB_SELECTION_NOTIFY => {
handle_selection_notify_event(event.ptr as *mut xcb_selection_notify_event_t);
}
XCB_PROPERTY_NOTIFY => {
handle_property_notify_event(event.ptr as *mut xcb_property_notify_event_t);
}
_ => {}
}
}
}
fn handle_selection_clear_event(event: *mut xcb_selection_clear_event_t) {
let selection = unsafe { (*event).selection };
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let locked = guard.as_mut().unwrap();
let clipboard_atom = { locked.shared.get_atom_by_id(CLIPBOARD) };
if selection == clipboard_atom {
locked.manager.clear_data();
}
}
fn handle_selection_request_event(event: *mut xcb_selection_request_event_t) {
let target;
let requestor;
let property;
let time;
let selection;
unsafe {
target = (*event).target;
requestor = (*event).requestor;
property = (*event).property;
time = (*event).time;
selection = (*event).selection;
}
let targets_atom;
let save_targets_atom;
let multiple_atom;
let atom_atom;
{
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let locked = guard.as_mut().unwrap();
let shared = &mut locked.shared;
targets_atom = shared.get_atom_by_id(TARGETS);
save_targets_atom = shared.get_atom_by_id(SAVE_TARGETS);
multiple_atom = shared.get_atom_by_id(MULTIPLE);
atom_atom = shared.get_atom_by_id(ATOM);
}
if target == targets_atom {
let mut targets = Atoms::with_capacity(4);
targets.push(targets_atom);
targets.push(save_targets_atom);
targets.push(multiple_atom);
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let locked = guard.as_mut().unwrap();
let manager = &locked.manager;
for atom in manager.data.keys() {
targets.push(*atom);
}
let shared = &locked.shared;
unsafe {
xcb_change_property(
shared.conn.as_ref().unwrap().get_raw_conn(),
XCB_PROP_MODE_REPLACE as u8,
requestor,
property,
atom_atom,
8 * std::mem::size_of::<xcb_atom_t>() as u8,
targets.len() as u32,
targets.as_ptr() as *const _,
)
};
} else if target == save_targets_atom {
} else if target == multiple_atom {
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let locked = guard.as_mut().unwrap();
let reply = {
let atom_pair_atom = locked.shared.get_atom_by_id(ATOM_PAIR);
get_and_delete_property(
locked.shared.conn.as_ref().unwrap(),
requestor,
property,
atom_pair_atom,
false,
)
};
if reply != std::ptr::null_mut() {
let mut ptr: *mut xcb_atom_t =
unsafe { xcb_get_property_value(reply) } as *mut xcb_atom_t;
let end = unsafe {
ptr.offset(
xcb_get_property_value_length(reply) as isize
/ std::mem::size_of::<xcb_atom_t>() as isize,
)
};
while ptr < end {
let target;
let property;
unsafe {
target = *ptr;
ptr = ptr.offset(1);
property = *ptr;
ptr = ptr.offset(1);
}
let property_set = locked.manager.set_requestor_property_with_clipboard_content(
&mut locked.shared,
requestor,
property,
target,
);
if !property_set {
unsafe {
xcb_change_property(
locked.shared.conn.as_ref().unwrap().get_raw_conn(),
XCB_PROP_MODE_REPLACE as u8,
requestor,
property,
XCB_ATOM_NONE,
0,
0,
std::ptr::null(),
)
};
}
}
unsafe {
libc::free(reply as *mut _);
}
}
} else {
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let locked = guard.as_mut().unwrap();
let property_set = locked.manager.set_requestor_property_with_clipboard_content(
&mut locked.shared,
requestor,
property,
target,
);
if !property_set {
return;
}
}
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let locked = guard.as_mut().unwrap();
let shared = &mut locked.shared;
let notify = xcb_selection_notify_event_t {
response_type: XCB_SELECTION_NOTIFY,
pad0: 0,
sequence: 0,
time,
requestor,
selection,
target,
property,
};
unsafe {
xcb_send_event(
shared.conn.as_ref().unwrap().get_raw_conn(),
0,
requestor,
XCB_EVENT_MASK_NO_EVENT,
¬ify as *const _ as *const _,
)
};
shared.conn.as_ref().unwrap().flush();
}
fn handle_selection_notify_event(event: *mut xcb_selection_notify_event_t) {
let target;
let requestor;
let property;
unsafe {
target = (*event).target;
requestor = (*event).requestor;
property = (*event).property;
}
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let mut locked = guard.as_mut().unwrap();
assert_eq!(requestor, locked.manager.window);
if target == locked.shared.get_atom_by_id(TARGETS) {
locked.manager.target_atom = locked.shared.get_atom_by_id(ATOM);
} else {
locked.manager.target_atom = target;
}
let target_atom = locked.manager.target_atom;
let mut reply = get_and_delete_property(
locked.shared.conn.as_ref().unwrap(),
requestor,
property,
target_atom,
true,
);
if reply != std::ptr::null_mut() {
let reply_type = unsafe { (*reply).type_ };
let incr_atom = locked.shared.get_atom_by_id(INCR);
if reply_type == incr_atom {
unsafe {
libc::free(reply as *mut _);
}
reply = get_and_delete_property(
locked.shared.conn.as_ref().unwrap(),
requestor,
property,
incr_atom,
true,
);
if reply != std::ptr::null_mut() {
if unsafe { xcb_get_property_value_length(reply) } == 4 {
let n = unsafe { *(xcb_get_property_value(reply) as *mut u32) };
locked.manager.reply_data = Some(Arc::new(Mutex::new(vec![0u8; n as usize])));
locked.manager.reply_offset = 0;
locked.manager.incr_process = true;
locked.manager.incr_received = true;
}
unsafe {
libc::free(reply as *mut _);
}
}
} else {
locked.manager.reply_data = None;
locked.manager.reply_offset = 0;
locked.manager.copy_reply_data(reply);
locked.manager.call_callback(reply);
unsafe {
libc::free(reply as *mut _);
}
}
}
}
fn handle_property_notify_event(event: *mut xcb_property_notify_event_t) {
let state;
let atom;
let window;
unsafe {
state = (*event).state as u32;
atom = (*event).atom;
window = (*event).window;
}
let mut guard = LOCKED_OBJECTS.lock().unwrap();
let mut locked = guard.as_mut().unwrap();
if locked.manager.incr_process
&& state == XCB_PROPERTY_NEW_VALUE
&& atom == locked.shared.get_atom_by_id(CLIPBOARD)
{
let target_atom = locked.manager.target_atom;
let reply = get_and_delete_property(
locked.shared.conn.as_ref().unwrap(),
window,
atom,
target_atom,
true,
);
if reply != std::ptr::null_mut() {
locked.manager.incr_received = true;
if unsafe { xcb_get_property_value_length(reply) } > 0 {
locked.manager.copy_reply_data(reply);
} else {
locked.manager.call_callback(reply);
locked.manager.incr_process = false;
}
unsafe {
libc::free(reply as *mut _);
}
}
}
}
fn get_and_delete_property(
conn: &xcb::base::Connection,
window: xproto::Window,
property: xproto::Atom,
atom: xproto::Atom,
delete_prop: bool,
) -> *mut xcb_get_property_reply_t {
let cookie = unsafe {
xcb_get_property(
conn.get_raw_conn(),
if delete_prop { 1 } else { 0 },
window,
property,
atom,
0,
0x1fffffff,
)
};
let mut err = std::ptr::null_mut();
let reply = unsafe { xcb_get_property_reply(conn.get_raw_conn(), cookie, &mut err as *mut _) };
if err != std::ptr::null_mut() {
unsafe {
libc::free(err as *mut _);
}
}
reply
}
fn get_data_from_selection_owner<'a>(
mut guard: MutexGuard<'a, Option<LockedObjects>>,
atoms: &Atoms,
callback: NotifyCallback,
mut selection: xproto::Atom,
) -> (bool, MutexGuard<'a, Option<LockedObjects>>) {
const CV_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
{
let locked = guard.as_mut().unwrap();
if selection == 0 {
selection = locked.shared.get_atom_by_id(CLIPBOARD);
}
locked.manager.callback = callback;
if locked.manager.window != get_x11_selection_owner(&mut locked.shared) {
locked.manager.data.clear();
}
}
for atom in atoms.iter() {
{
let locked = guard.as_mut().unwrap();
let clipboard_atom = locked.shared.get_atom_by_id(CLIPBOARD);
xproto::convert_selection(
locked.shared.conn.as_ref().unwrap(),
locked.manager.window,
selection,
*atom,
clipboard_atom,
xcb::base::CURRENT_TIME,
);
locked.shared.conn.as_ref().unwrap().flush();
}
'incr_loop: loop {
guard.as_mut().unwrap().manager.incr_received = false;
match CONDVAR.wait_timeout(guard, CV_TIMEOUT) {
Ok((new_guard, status)) => {
guard = new_guard;
if !status.timed_out() {
return (guard.as_ref().unwrap().manager.callback_result, guard);
}
if !guard.as_ref().unwrap().manager.incr_received {
break 'incr_loop;
}
}
Err(err) => {
panic!(
"A critical error occured while working with the x11 clipboard. {}",
err
);
}
}
}
}
guard.as_mut().unwrap().manager.callback = None;
(false, guard)
}
fn get_x11_selection_owner(shared: &mut SharedState) -> xcb::xproto::Window {
let mut result = 0;
let clipboard_atom = shared.get_atom_by_id(CLIPBOARD);
let cookie = xproto::get_selection_owner(shared.conn.as_ref().unwrap(), clipboard_atom);
let reply = unsafe {
xcb_get_selection_owner_reply(
shared.conn.as_ref().unwrap().get_raw_conn(),
cookie.cookie,
std::ptr::null_mut(),
)
};
if reply != std::ptr::null_mut() {
result = unsafe { (*reply).owner };
unsafe {
libc::free(reply as *mut _);
}
}
result
}
fn get_text(mut guard: MutexGuard<Option<LockedObjects>>) -> Result<String, Error> {
let owner = get_x11_selection_owner(&mut guard.as_mut().unwrap().shared);
if owner == guard.as_mut().unwrap().manager.window {
let atoms = guard.as_mut().unwrap().shared.get_text_format_atoms().clone();
for atom in atoms.iter() {
let mut item = None;
if let Some(i) = guard.as_mut().unwrap().manager.data.get(atom) {
if let Some(i) = i {
item = Some(i.clone());
}
}
if let Some(item) = item {
let locked = item.lock().unwrap();
let result = String::from_utf8(locked.clone());
return Ok(result.map_err(|_| Error::ConversionFailure)?);
}
}
} else if owner != 0 {
let atoms = guard.as_mut().unwrap().shared.get_text_format_atoms().clone();
let result = Arc::new(Mutex::new(Ok(String::new())));
let callback = {
let result = result.clone();
Arc::new(move |data: &BufferPtr| {
if let Some(reply_data) = data {
let locked_data = reply_data.lock().unwrap();
let mut locked_result = result.lock().unwrap();
*locked_result = String::from_utf8(locked_data.clone());
}
true
})
};
let (success, _) = get_data_from_selection_owner(guard, &atoms, Some(callback as _), 0);
if success {
let mut taken = Ok(String::new());
let mut locked = result.lock().unwrap();
std::mem::swap(&mut taken, &mut locked);
return Ok(taken.map_err(|_| Error::ConversionFailure)?);
}
}
Err(Error::ContentNotAvailable)
}
fn get_image(mut guard: MutexGuard<Option<LockedObjects>>) -> Result<ImageData, Error> {
let owner = get_x11_selection_owner(&mut guard.as_mut().unwrap().shared);
if owner == guard.as_ref().unwrap().manager.window {
let image = &guard.as_ref().unwrap().manager.image;
if image.width > 0 && image.height > 0 && !image.bytes.is_empty() {
return Ok(image.to_cloned());
}
} else if owner != 0 {
let atoms = vec![guard.as_mut().unwrap().shared.get_atom_by_id(MIME_IMAGE_PNG)];
let result: Arc<Mutex<Result<ImageData, Error>>> =
Arc::new(Mutex::new(Err(Error::ContentNotAvailable)));
let callback = {
let result = result.clone();
Arc::new(move |data: &BufferPtr| {
if let Some(reply_data) = data {
let locked_data = reply_data.lock().unwrap();
let cursor = std::io::Cursor::new(&*locked_data);
let mut reader = image::io::Reader::new(cursor);
reader.set_format(image::ImageFormat::Png);
let image;
match reader.decode() {
Ok(img) => image = img.into_rgba(),
Err(_e) => {
let mut locked_result = result.lock().unwrap();
*locked_result = Err(Error::ConversionFailure);
return false;
}
}
let (w, h) = image.dimensions();
let mut locked_result = result.lock().unwrap();
let image_data = ImageData {
width: w as usize,
height: h as usize,
bytes: image.into_raw().into(),
};
*locked_result = Ok(image_data);
}
true
})
};
let _success = get_data_from_selection_owner(guard, &atoms, Some(callback as _), 0).0;
let mut taken = Err(Error::Unknown {
description: format!("Implementation error at {}:{}", file!(), line!()),
});
let mut locked = result.lock().unwrap();
std::mem::swap(&mut taken, &mut locked);
return Ok(taken?);
}
Err(Error::ContentNotAvailable)
}
fn encode_data_on_demand(
shared: &mut SharedState,
image: &mut ImageData,
atom: xproto::Atom,
buffer: &mut Option<Arc<Mutex<Vec<u8>>>>,
) {
#[derive(Clone)]
struct RcBuffer {
inner: Rc<RefCell<Vec<u8>>>,
}
impl std::io::Write for RcBuffer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.borrow_mut().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
if atom == shared.get_atom_by_id(MIME_IMAGE_PNG) {
if image.bytes.is_empty() || image.width == 0 || image.height == 0 {
return;
}
let output = RcBuffer { inner: Rc::new(RefCell::new(Vec::new())) };
let encoding_result;
{
let encoder = image::png::PngEncoder::new(output.clone());
encoding_result = encoder.encode(
image.bytes.as_ref(),
image.width as u32,
image.height as u32,
image::ColorType::Rgba8,
);
}
if encoding_result.is_ok() {
*buffer =
Some(Arc::new(Mutex::new(Rc::try_unwrap(output.inner).unwrap().into_inner())));
}
}
}
fn ensure_lo_initialized() -> Result<MutexGuard<'static, Option<LockedObjects>>, Error> {
let mut locked = LOCKED_OBJECTS.lock().unwrap();
if locked.is_none() {
*locked = Some(LockedObjects::new().map_err(|e| Error::Unknown {
description: format!(
"Could not initialize the x11 clipboard handling facilities. Cause: {}",
e
),
})?);
}
Ok(locked)
}
fn with_locked_objects<F, T>(action: F) -> Result<T, Error>
where
F: FnOnce(&mut LockedObjects) -> Result<T, Error>,
{
let mut locked = ensure_lo_initialized()?;
let lo = locked.as_mut().unwrap();
action(lo)
}
pub struct X11ClipboardContext {
_owned: Arc<Mutex<Option<LockedObjects>>>,
}
impl Drop for X11ClipboardContext {
fn drop(&mut self) {
if Arc::strong_count(&LOCKED_OBJECTS) == 2 {
Manager::destruct();
let mut locked = LOCKED_OBJECTS.lock().unwrap();
*locked = None;
}
}
}
impl X11ClipboardContext {
pub(crate) fn new() -> Result<Self, Error> {
Ok(X11ClipboardContext { _owned: LOCKED_OBJECTS.clone() })
}
pub(crate) fn get_text(&mut self) -> Result<String, Error> {
let locked = ensure_lo_initialized()?;
get_text(locked)
}
pub(crate) fn set_text(&mut self, text: String) -> Result<(), Error> {
with_locked_objects(|locked| {
let manager = &mut locked.manager;
let shared = &mut locked.shared;
manager.set_text(shared, text.into_bytes())
})
}
pub(crate) fn get_image(&mut self) -> Result<ImageData, Error> {
let locked = ensure_lo_initialized()?;
get_image(locked)
}
pub(crate) fn set_image(&mut self, image: ImageData) -> Result<(), Error> {
with_locked_objects(|locked| {
let manager = &mut locked.manager;
let shared = &mut locked.shared;
manager.set_image(shared, image)
})
}
}