fix: keep clipboard alive

Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
ClSlaid 2023-10-28 15:31:12 +08:00
parent 075a877284
commit 9976fc9723
No known key found for this signature in database
GPG Key ID: E0A5F564C51C056E
4 changed files with 71 additions and 27 deletions

2
Cargo.lock generated
View File

@ -5257,9 +5257,11 @@ dependencies = [
"num_cpus", "num_cpus",
"objc", "objc",
"objc_id", "objc_id",
"once_cell",
"os-version", "os-version",
"pam", "pam",
"parity-tokio-ipc", "parity-tokio-ipc",
"percent-encoding",
"rdev", "rdev",
"repng", "repng",
"reqwest", "reqwest",

View File

@ -134,6 +134,8 @@ pam = { git="https://github.com/fufesou/pam", optional = true }
users = { version = "0.11" } users = { version = "0.11" }
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch"} x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch"}
x11rb = {version = "0.12", features = ["all-extensions"]} x11rb = {version = "0.12", features = ["all-extensions"]}
percent-encoding = "2.3"
once_cell = "1.18"
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.13" android_logger = "0.13"

View File

@ -67,9 +67,12 @@ impl X11Clipboard {
// NOTE: // NOTE:
// # why not use `load_wait` // # why not use `load_wait`
// load_wait is likely to wait forever, which is not what we want // load_wait is likely to wait forever, which is not what we want
get_clip()? let res = get_clip()?.load(clip, target, prop, X11_CLIPBOARD_TIMEOUT);
.load(clip, target, prop, X11_CLIPBOARD_TIMEOUT) match res {
.map_err(|_| CliprdrError::ConversionFailure) Ok(res) => Ok(res),
Err(x11_clipboard::error::Error::UnexpectedType(_)) => Ok(vec![]),
Err(_) => Err(CliprdrError::ClipboardInternalError),
}
} }
fn store_batch(&self, batch: Vec<(Atom, Vec<u8>)>) -> Result<(), CliprdrError> { fn store_batch(&self, batch: Vec<(Atom, Vec<u8>)>) -> Result<(), CliprdrError> {
@ -85,7 +88,6 @@ impl X11Clipboard {
return Ok(None); return Ok(None);
} }
let v = self.load(self.text_uri_list)?; let v = self.load(self.text_uri_list)?;
// loading 'text/uri-list' should be enough?
let p = parse_plain_uri_list(v)?; let p = parse_plain_uri_list(v)?;
Ok(Some(p)) Ok(Some(p))
} }

View File

@ -18,70 +18,108 @@ pub enum GrabState {
)))] )))]
pub use arboard::Clipboard as ClipboardContext; pub use arboard::Clipboard as ClipboardContext;
#[cfg(all(target_os = "linux", not(feature = "wayland")))]
static X11_CLIPBOARD: once_cell::sync::OnceCell<x11_clipboard::Clipboard> =
once_cell::sync::OnceCell::new();
#[cfg(all(target_os = "linux", not(feature = "wayland")))]
fn get_clipboard() -> Result<&'static x11_clipboard::Clipboard, String> {
X11_CLIPBOARD
.get_or_try_init(|| x11_clipboard::Clipboard::new())
.map_err(|e| e.to_string())
}
#[cfg(all(target_os = "linux", not(feature = "wayland")))] #[cfg(all(target_os = "linux", not(feature = "wayland")))]
pub struct ClipboardContext { pub struct ClipboardContext {
string_setter: x11rb::protocol::xproto::Atom, string_setter: x11rb::protocol::xproto::Atom,
string_getter: x11rb::protocol::xproto::Atom, string_getter: x11rb::protocol::xproto::Atom,
text_uri_list: x11rb::protocol::xproto::Atom, text_uri_list: x11rb::protocol::xproto::Atom,
clip: x11_clipboard::Clipboard,
clip: x11rb::protocol::xproto::Atom,
prop: x11rb::protocol::xproto::Atom,
}
#[cfg(all(target_os = "linux", not(feature = "wayland")))]
fn parse_plain_uri_list(v: Vec<u8>) -> Result<String, String> {
let text = String::from_utf8(v).map_err(|_| "ConversionFailure".to_owned())?;
let mut list = String::new();
for line in text.lines() {
if !line.starts_with("file://") {
continue;
}
let decoded = percent_encoding::percent_decode_str(line)
.decode_utf8()
.map_err(|_| "ConversionFailure".to_owned())?;
list = list + "\n" + decoded.trim_start_matches("file://");
}
list = list.trim().to_owned();
Ok(list)
} }
#[cfg(all(target_os = "linux", not(feature = "wayland")))] #[cfg(all(target_os = "linux", not(feature = "wayland")))]
impl ClipboardContext { impl ClipboardContext {
pub fn new() -> Result<Self, String> { pub fn new() -> Result<Self, String> {
log::debug!("create clipboard context"); let clipboard = get_clipboard()?;
let clip = x11_clipboard::Clipboard::new().map_err(|e| e.to_string())?; let string_getter = clipboard
let string_getter = clip
.getter .getter
.get_atom("UTF8_STRING") .get_atom("UTF8_STRING")
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let string_setter = clip let string_setter = clipboard
.setter .setter
.get_atom("UTF8_STRING") .get_atom("UTF8_STRING")
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let text_uri_list = clip let text_uri_list = clipboard
.getter .getter
.get_atom("text/uri-list") .get_atom("text/uri-list")
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
log::debug!("clipboard context created"); let prop = clipboard.getter.atoms.property;
let clip = clipboard.getter.atoms.clipboard;
Ok(Self { Ok(Self {
clip,
text_uri_list, text_uri_list,
string_setter, string_setter,
string_getter, string_getter,
clip,
prop,
}) })
} }
pub fn get_text(&mut self) -> Result<String, String> { pub fn get_text(&mut self) -> Result<String, String> {
let clip = self.clip.getter.atoms.clipboard; let clip = self.clip;
let prop = self.clip.getter.atoms.property; let prop = self.prop;
log::trace!("clipboard get text");
const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100); const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
let text_content = self let text_content = get_clipboard()?
.clip
.load(clip, self.string_getter, prop, TIMEOUT) .load(clip, self.string_getter, prop, TIMEOUT)
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let file_urls = self.clip.load(clip, self.text_uri_list, prop, TIMEOUT); let file_urls = get_clipboard()?.load(clip, self.text_uri_list, prop, TIMEOUT);
if file_urls.is_err() || file_urls.as_ref().unwrap().is_empty() { if file_urls.is_err() || file_urls.as_ref().unwrap().is_empty() {
log::trace!("clipboard get text, no file urls"); log::debug!("clipboard get text, no file urls");
String::from_utf8(text_content).map_err(|e| e.to_string()) return String::from_utf8(text_content).map_err(|e| e.to_string());
} else {
log::trace!("clipboard get text, file urls found");
Err("clipboard polluted".to_owned())
} }
let file_urls = parse_plain_uri_list(file_urls.unwrap())?;
let text_content = String::from_utf8(text_content).map_err(|e| e.to_string())?;
if text_content.trim() == file_urls.trim() {
log::debug!("clipboard got text but polluted");
return Err(String::from("polluted text"));
}
Ok(text_content)
} }
pub fn set_text(&mut self, content: String) -> Result<(), String> { pub fn set_text(&mut self, content: String) -> Result<(), String> {
let clip = self.clip.setter.atoms.clipboard; let clip = self.clip;
let value = content.into_bytes(); let value = content.clone().into_bytes();
self.clip get_clipboard()?
.store(clip, self.string_setter, value) .store(clip, self.string_setter, value)
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())?;
Ok(())
} }
} }