use crate::error::TuiError;
#[cfg(feature = "clipboard")]
fn is_ssh() -> bool {
std::env::var_os("SSH_TTY").is_some()
|| std::env::var_os("SSH_CONNECTION").is_some()
|| std::env::var_os("SSH_CLIENT").is_some()
}
#[cfg(feature = "clipboard")]
fn write_osc52(text: &str) -> Result<(), TuiError> {
use base64::Engine as _;
use std::io::Write as _;
let encoded = base64::engine::general_purpose::STANDARD.encode(text.as_bytes());
let seq = format!("\x1b]52;c;{encoded}\x07");
let mut stderr = std::io::stderr();
stderr.write_all(seq.as_bytes()).map_err(TuiError::from)?;
stderr.flush().map_err(TuiError::from)?;
Ok(())
}
pub struct ClipboardHandle {
#[cfg(feature = "clipboard")]
inner: Option<arboard::Clipboard>,
#[cfg(feature = "clipboard")]
is_ssh: bool,
}
impl ClipboardHandle {
#[must_use]
pub fn new() -> Self {
#[cfg(feature = "clipboard")]
{
let is_ssh = is_ssh();
let inner = arboard::Clipboard::new().ok();
Self { inner, is_ssh }
}
#[cfg(not(feature = "clipboard"))]
Self {}
}
pub fn copy(&mut self, text: &str) -> Result<(), TuiError> {
#[cfg(feature = "clipboard")]
{
if self.is_ssh {
return write_osc52(text);
}
if let Some(ref mut cb) = self.inner
&& cb.set_text(text).is_ok()
{
return Ok(());
}
write_osc52(text)
}
#[cfg(not(feature = "clipboard"))]
{
let _ = text;
Ok(())
}
}
}
impl Default for ClipboardHandle {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clipboard_handle_default_does_not_panic() {
let _ = ClipboardHandle::default();
}
#[test]
fn clipboard_handle_new_does_not_panic() {
let _ = ClipboardHandle::new();
}
#[cfg(feature = "clipboard")]
#[test]
fn is_ssh_false_when_env_absent() {
if std::env::var_os("SSH_TTY").is_none()
&& std::env::var_os("SSH_CONNECTION").is_none()
&& std::env::var_os("SSH_CLIENT").is_none()
{
assert!(!is_ssh());
}
}
#[cfg(feature = "clipboard")]
#[test]
fn osc52_base64_encodes_payload() {
use base64::Engine as _;
let text = "hello clipboard";
let encoded = base64::engine::general_purpose::STANDARD.encode(text.as_bytes());
assert!(
encoded
.chars()
.all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '=')
);
}
}