use async_trait::async_trait;
use crate::{Backend, BackendKind, Capabilities, ClipboardError, MimeType, Selection};
use super::osc52::Osc52Backend;
pub struct SshAwareBackend {
inner: Box<dyn Backend>,
osc52: Osc52Backend,
}
impl SshAwareBackend {
pub fn new(inner: Box<dyn Backend>) -> Self {
Self {
inner,
osc52: Osc52Backend::new(),
}
}
fn should_fallback(err: &ClipboardError) -> bool {
matches!(
err,
ClipboardError::BackendUnavailable
| ClipboardError::UnsupportedMime
| ClipboardError::NoDisplay
| ClipboardError::FocusRequired
)
}
}
#[async_trait]
impl Backend for SshAwareBackend {
fn kind(&self) -> BackendKind {
BackendKind::SshAware
}
fn capabilities(&self) -> Capabilities {
self.inner.capabilities() | self.osc52.capabilities()
}
fn set(&self, sel: Selection, mime: MimeType, bytes: &[u8]) -> Result<(), ClipboardError> {
match self.inner.set(sel, mime.clone(), bytes) {
Err(e) if Self::should_fallback(&e) => self.osc52.set(sel, mime, bytes),
other => other,
}
}
fn get(&self, sel: Selection, mime: MimeType) -> Result<Vec<u8>, ClipboardError> {
self.inner.get(sel, mime)
}
fn clear(&self, sel: Selection) -> Result<(), ClipboardError> {
match self.inner.clear(sel) {
Err(e) if Self::should_fallback(&e) => self.osc52.clear(sel),
other => other,
}
}
fn available(&self, sel: Selection) -> Result<Vec<MimeType>, ClipboardError> {
self.inner.available(sel)
}
async fn set_async(
&self,
sel: Selection,
mime: MimeType,
bytes: Vec<u8>,
) -> Result<(), ClipboardError> {
let inner_caps = self.inner.capabilities();
let primary = if inner_caps.contains(Capabilities::ASYNC_WRITE) {
self.inner.set_async(sel, mime.clone(), bytes.clone()).await
} else {
self.inner.set(sel, mime.clone(), &bytes)
};
match primary {
Err(e) if Self::should_fallback(&e) => self.osc52.set(sel, mime, &bytes),
other => other,
}
}
async fn get_async(&self, sel: Selection, mime: MimeType) -> Result<Vec<u8>, ClipboardError> {
if self.inner.capabilities().contains(Capabilities::ASYNC_READ) {
self.inner.get_async(sel, mime).await
} else {
self.inner.get(sel, mime)
}
}
async fn clear_async(&self, sel: Selection) -> Result<(), ClipboardError> {
let primary = if self
.inner
.capabilities()
.contains(Capabilities::ASYNC_CLEAR)
{
self.inner.clear_async(sel).await
} else {
self.inner.clear(sel)
};
match primary {
Err(e) if Self::should_fallback(&e) => self.osc52.clear(sel),
other => other,
}
}
async fn available_async(&self, sel: Selection) -> Result<Vec<MimeType>, ClipboardError> {
if self
.inner
.capabilities()
.contains(Capabilities::ASYNC_AVAILABLE)
{
self.inner.available_async(sel).await
} else {
self.inner.available(sel)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Clipboard;
use crate::backend::mock::MockBackend;
#[test]
fn falls_back_to_osc52_on_unsupported_mime() {
let inner = MockBackend::new(BackendKind::Mock, Capabilities::WRITE);
struct FailingInner;
#[async_trait]
impl Backend for FailingInner {
fn kind(&self) -> BackendKind {
BackendKind::Mock
}
fn capabilities(&self) -> Capabilities {
Capabilities::WRITE
}
fn set(&self, _: Selection, _: MimeType, _: &[u8]) -> Result<(), ClipboardError> {
Err(ClipboardError::UnsupportedMime)
}
fn get(&self, _: Selection, _: MimeType) -> Result<Vec<u8>, ClipboardError> {
Err(ClipboardError::UnsupportedMime)
}
fn clear(&self, _: Selection) -> Result<(), ClipboardError> {
Err(ClipboardError::UnsupportedMime)
}
fn available(&self, _: Selection) -> Result<Vec<MimeType>, ClipboardError> {
Ok(Vec::new())
}
}
let _ = inner;
let ssh = SshAwareBackend::new(Box::new(FailingInner));
let cb = Clipboard::with_backend(Box::new(ssh));
assert!(cb.set(Selection::Clipboard, MimeType::Text, b"hi").is_ok());
}
#[test]
fn capabilities_are_union() {
let inner = MockBackend::new(BackendKind::Mock, Capabilities::READ);
let ssh = SshAwareBackend::new(Box::new(inner));
let caps = ssh.capabilities();
assert!(caps.contains(Capabilities::READ), "inner READ propagated");
assert!(
caps.contains(Capabilities::WRITE),
"OSC 52 WRITE propagated"
);
assert!(
caps.contains(Capabilities::CLEAR),
"OSC 52 CLEAR propagated"
);
}
#[test]
fn kind_reports_ssh_aware() {
let inner = MockBackend::new(BackendKind::Mock, Capabilities::all());
let ssh = SshAwareBackend::new(Box::new(inner));
assert_eq!(ssh.kind(), BackendKind::SshAware);
}
}