use crate::{CallbackResult, ClipboardHandler};
use std::io;
use std::sync::OnceLock;
use std::sync::mpsc::{self, SyncSender, Receiver, sync_channel};
use x11rb::protocol::xfixes;
use x11rb::connection::Connection;
use x11rb::protocol::xproto::ConnectionExt;
pub struct Shutdown {
sender: SyncSender<()>,
}
impl Drop for Shutdown {
#[inline(always)]
fn drop(&mut self) {
let _ = self.sender.send(());
}
}
pub struct Master<H> {
handler: H,
sender: SyncSender<()>,
recv: Receiver<()>
}
impl<H: ClipboardHandler> Master<H> {
#[inline(always)]
pub fn new(handler: H) -> io::Result<Self> {
let (sender, recv) = sync_channel(0);
Ok(Self {
handler,
sender,
recv,
})
}
#[inline(always)]
pub fn shutdown_channel(&self) -> Shutdown {
Shutdown {
sender: self.sender.clone()
}
}
pub fn run(&mut self) -> io::Result<()> {
let clipboard = match Self::x11_clipboard() {
Ok(clipboard) => clipboard,
Err(error) => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to initialize clipboard: {:?}", error),
))
}
};
if let Err(error) = xfixes::query_version(&clipboard.getter.connection, 5, 0) {
return Err(io::Error::new(io::ErrorKind::Other, error));
}
let mut result = Ok(());
'main: loop {
let selection = clipboard.getter.atoms.clipboard;
let screen = match clipboard.getter.connection.setup().roots.get(clipboard.getter.screen) {
Some(screen) => screen,
None => match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, "Screen is not available")) {
CallbackResult::Next => continue,
CallbackResult::Stop => break,
CallbackResult::StopWithError(error) => {
result = Err(error);
break;
}
}
};
let cookie = xfixes::select_selection_input(
&clipboard.getter.connection,
screen.root,
clipboard.getter.atoms.primary,
xfixes::SelectionEventMask::default()
).and_then(|_| xfixes::select_selection_input(
&clipboard.getter.connection,
screen.root,
clipboard.getter.atoms.clipboard,
xfixes::SelectionEventMask::default()
)).and_then(|_| xfixes::select_selection_input(
&clipboard.getter.connection,
screen.root,
selection,
xfixes::SelectionEventMask::SET_SELECTION_OWNER | xfixes::SelectionEventMask::SELECTION_CLIENT_CLOSE | xfixes::SelectionEventMask::SELECTION_WINDOW_DESTROY
));
if let Err(error) = clipboard.getter.connection.flush() {
match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, error)) {
CallbackResult::Next => continue,
CallbackResult::Stop => break,
CallbackResult::StopWithError(error) => {
result = Err(error);
break;
}
}
}
let sequence_number = match cookie {
Ok(cookie) => {
let sequence_number = cookie.sequence_number();
if let Err(error) = cookie.check() {
match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, error)) {
CallbackResult::Next => continue,
CallbackResult::Stop => break,
CallbackResult::StopWithError(error) => {
result = Err(error);
break;
}
}
}
sequence_number
},
Err(error) => match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, error)) {
CallbackResult::Next => continue,
CallbackResult::Stop => break,
CallbackResult::StopWithError(error) => {
result = Err(error);
break;
}
}
};
'poll: loop {
match clipboard.getter.connection.poll_for_event_with_sequence() {
Ok(Some((_, seq))) if seq >= sequence_number => {
match self.handler.on_clipboard_change() {
CallbackResult::Next => break 'poll,
CallbackResult::Stop => break 'main,
CallbackResult::StopWithError(error) => {
result = Err(error);
break 'main;
}
}
},
Ok(_) => {
match self.recv.recv_timeout(self.handler.sleep_interval()) {
Ok(()) => break 'main,
Err(mpsc::RecvTimeoutError::Timeout) => continue 'poll,
Err(mpsc::RecvTimeoutError::Disconnected) => break 'main,
}
}
Err(error) => {
let error = io::Error::new(
io::ErrorKind::Other,
format!("Failed to load clipboard: {:?}", error),
);
match self.handler.on_clipboard_error(error) {
CallbackResult::Next => break 'poll,
CallbackResult::Stop => break 'main,
CallbackResult::StopWithError(error) => {
result = Err(error);
break 'main;
}
}
}
}
}
let delete = clipboard.getter.connection.delete_property(clipboard.getter.window, clipboard.getter.atoms.property)
.map_err(|error| io::Error::new(io::ErrorKind::Other, error))
.and_then(|cookie| cookie.check().map_err(|error| io::Error::new(io::ErrorKind::Other, error)));
if let Err(error) = delete {
match self.handler.on_clipboard_error(error) {
CallbackResult::Next => (),
CallbackResult::Stop => break,
CallbackResult::StopWithError(error) => {
result = Err(error);
break;
}
}
}
match self.recv.recv_timeout(self.handler.sleep_interval()) {
Ok(()) => break,
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(mpsc::RecvTimeoutError::Disconnected) => break,
}
}
match clipboard.getter.connection.delete_property(clipboard.getter.window, clipboard.getter.atoms.property) {
Ok(cookie) => match cookie.check() {
Ok(_) => result,
Err(error) => Err(io::Error::new(io::ErrorKind::Other, error)),
},
Err(error) => Err(io::Error::new(io::ErrorKind::Other, error)),
}
}
pub fn x11_clipboard() -> &'static Result<x11_clipboard::Clipboard, x11_clipboard::error::Error> {
static CLIP: OnceLock<Result<x11_clipboard::Clipboard, x11_clipboard::error::Error>> = OnceLock::new();
CLIP.get_or_init(x11_clipboard::Clipboard::new)
}
}