mirror of
https://github.com/Noratrieb/clippyboard.git
synced 2026-01-14 09:55:04 +01:00
toll
This commit is contained in:
parent
1164ed1e3e
commit
fbce93b927
3 changed files with 154 additions and 71 deletions
187
src/daemon.rs
187
src/daemon.rs
|
|
@ -17,51 +17,16 @@ use wl_clipboard_rs::paste::Seat;
|
|||
pub(crate) fn handle_peer(
|
||||
mut peer: UnixStream,
|
||||
next_id: Arc<AtomicU64>,
|
||||
last_copied: Arc<AtomicU64>,
|
||||
items: Arc<Mutex<Vec<Entry>>>,
|
||||
) -> eyre::Result<()> {
|
||||
let mut request = [0; 1];
|
||||
peer.read_exact(&mut request)
|
||||
.wrap_err("failed to read message type")?;
|
||||
let Ok(()) = peer.read_exact(&mut request) else {
|
||||
return Ok(());
|
||||
};
|
||||
match request[0] {
|
||||
super::MESSAGE_STORE => {
|
||||
let mime_types =
|
||||
wl_clipboard_rs::paste::get_mime_types(ClipboardType::Regular, Seat::Unspecified)
|
||||
.wrap_err("getting mime types")?;
|
||||
|
||||
let time = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
|
||||
let Some(mime) = ["text/plain", "image/png"]
|
||||
.iter()
|
||||
.find(|mime| mime_types.contains(**mime))
|
||||
else {
|
||||
eprintln!("WARN: No supported mime type found. Found mime types: {mime_types:?}");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let (data_readear, _) = wl_clipboard_rs::paste::get_contents(
|
||||
ClipboardType::Regular,
|
||||
Seat::Unspecified,
|
||||
MimeType::Specific(mime),
|
||||
)
|
||||
.wrap_err("getting contents")?;
|
||||
|
||||
let mut data_reader = data_readear.take(MAX_ENTRY_SIZE);
|
||||
|
||||
let mut data = Vec::new();
|
||||
data_reader
|
||||
.read_to_end(&mut data)
|
||||
.wrap_err("reading content data")?;
|
||||
|
||||
items.lock().unwrap().push(Entry {
|
||||
id: next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
||||
mime: mime.to_string(),
|
||||
data,
|
||||
created_time: u64::try_from(time.as_millis()).unwrap(),
|
||||
});
|
||||
|
||||
println!("INFO Successfully stored clipboard value of mime type {mime}");
|
||||
handle_store(next_id, last_copied, &items).wrap_err("handling store message")?;
|
||||
}
|
||||
super::MESSAGE_READ => {
|
||||
let items = items.lock().unwrap();
|
||||
|
|
@ -70,35 +35,126 @@ pub(crate) fn handle_peer(
|
|||
.wrap_err("writing items to socket")?;
|
||||
}
|
||||
super::MESSAGE_COPY => {
|
||||
let mut id = [0; 8];
|
||||
peer.read_exact(&mut id).wrap_err("failed to read id")?;
|
||||
let id = u64::from_le_bytes(id);
|
||||
|
||||
let items = items.lock().unwrap();
|
||||
|
||||
let Some(idx) = items.iter().position(|item| item.id == id) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let entry = items[idx].clone();
|
||||
|
||||
// select
|
||||
let mut opts = wl_clipboard_rs::copy::Options::new();
|
||||
opts.clipboard(wl_clipboard_rs::copy::ClipboardType::Regular);
|
||||
let result = wl_clipboard_rs::copy::copy(
|
||||
opts,
|
||||
wl_clipboard_rs::copy::Source::Bytes(entry.data.into_boxed_slice()),
|
||||
wl_clipboard_rs::copy::MimeType::Specific(entry.mime),
|
||||
);
|
||||
if let Err(err) = result {
|
||||
println!("WARNING: Copy failed: {err:?}");
|
||||
}
|
||||
handle_copy(peer, last_copied, items).wrap_err("handling copy message")?;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_copy(
|
||||
mut peer: UnixStream,
|
||||
last_copied: Arc<AtomicU64>,
|
||||
items: Arc<Mutex<Vec<Entry>>>,
|
||||
) -> Result<(), eyre::Error> {
|
||||
let mut id = [0; 8];
|
||||
peer.read_exact(&mut id).wrap_err("failed to read id")?;
|
||||
let id = u64::from_le_bytes(id);
|
||||
let mut items = items.lock().unwrap();
|
||||
let Some(idx) = items.iter().position(|item| item.id == id) else {
|
||||
return Ok(());
|
||||
};
|
||||
let entry = items.remove(idx);
|
||||
items.push(entry.clone());
|
||||
let mut opts = wl_clipboard_rs::copy::Options::new();
|
||||
opts.clipboard(wl_clipboard_rs::copy::ClipboardType::Regular)
|
||||
.seat(wl_clipboard_rs::copy::Seat::All);
|
||||
let result = wl_clipboard_rs::copy::copy(
|
||||
opts,
|
||||
wl_clipboard_rs::copy::Source::Bytes(entry.data.into_boxed_slice()),
|
||||
wl_clipboard_rs::copy::MimeType::Specific(entry.mime),
|
||||
);
|
||||
last_copied.store(entry.id, std::sync::atomic::Ordering::Relaxed);
|
||||
if let Err(err) = result {
|
||||
println!("WARNING: Copy failed: {err:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_store(
|
||||
next_id: Arc<AtomicU64>,
|
||||
last_copied: Arc<AtomicU64>,
|
||||
items: &Arc<Mutex<Vec<Entry>>>,
|
||||
) -> Result<(), eyre::Error> {
|
||||
let time = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
|
||||
let mime_types =
|
||||
wl_clipboard_rs::paste::get_mime_types(ClipboardType::Regular, Seat::Unspecified)
|
||||
.wrap_err("getting mime types")?;
|
||||
|
||||
let Some(mime) = ["text/plain", "image/png"]
|
||||
.iter()
|
||||
.find(|mime| mime_types.contains(**mime))
|
||||
else {
|
||||
eprintln!("WARN: No supported mime type found. Found mime types: {mime_types:?}");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let (data_readear, _) = wl_clipboard_rs::paste::get_contents(
|
||||
ClipboardType::Regular,
|
||||
Seat::Unspecified,
|
||||
MimeType::Specific(mime),
|
||||
)
|
||||
.wrap_err("getting contents")?;
|
||||
|
||||
let mut data_reader = data_readear.take(MAX_ENTRY_SIZE);
|
||||
let mut data = Vec::new();
|
||||
data_reader
|
||||
.read_to_end(&mut data)
|
||||
.wrap_err("reading content data")?;
|
||||
|
||||
let new_entry = Entry {
|
||||
id: next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
||||
mime: mime.to_string(),
|
||||
data,
|
||||
created_time: u64::try_from(time.as_millis()).unwrap(),
|
||||
};
|
||||
|
||||
let mut items = items.lock().unwrap();
|
||||
if items
|
||||
.last()
|
||||
.is_some_and(|last| last.mime == new_entry.mime && last.data == new_entry.data)
|
||||
{
|
||||
println!("INFO: Skipping store of new item because it is identical to last one");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let last_copied = last_copied.load(std::sync::atomic::Ordering::Relaxed);
|
||||
if let Some(item) = items.iter().find(|item| item.id == last_copied)
|
||||
&& item.mime == new_entry.mime
|
||||
&& item.data == new_entry.data
|
||||
{
|
||||
println!("INFO: Skipping store of new item because the copy came from us");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
items.push(new_entry);
|
||||
|
||||
let mut running_total = 0;
|
||||
let mut cutoff = None;
|
||||
for (idx, item) in items.iter().enumerate().rev() {
|
||||
running_total += item.data.len() + std::mem::size_of::<Entry>();
|
||||
if running_total > crate::MAX_HISTORY_BYTE_SIZE {
|
||||
cutoff = Some(idx);
|
||||
}
|
||||
}
|
||||
if let Some(cutoff) = cutoff {
|
||||
println!(
|
||||
"INFO: Dropping old {} items because limit of {} bytes was reached for the history",
|
||||
cutoff + 1,
|
||||
crate::MAX_HISTORY_BYTE_SIZE
|
||||
);
|
||||
items.splice(0..=cutoff, []);
|
||||
}
|
||||
|
||||
println!(
|
||||
"INFO: Successfully stored clipboard value of mime type {mime} (new history size {running_total})"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn main(socket_path: &PathBuf) -> eyre::Result<()> {
|
||||
let _ = std::fs::remove_file(&socket_path); // lol
|
||||
let socket = UnixListener::bind(&socket_path)
|
||||
|
|
@ -106,6 +162,8 @@ pub fn main(socket_path: &PathBuf) -> eyre::Result<()> {
|
|||
|
||||
let next_id = Arc::new(AtomicU64::new(0));
|
||||
let items = Arc::new(Mutex::new(Vec::<Entry>::new()));
|
||||
// for deduplication because the event stream will tell us that we just copied something :)
|
||||
let last_copied = Arc::new(AtomicU64::new(u64::MAX));
|
||||
|
||||
println!("INFO: Listening on {}", socket_path.display());
|
||||
|
||||
|
|
@ -114,8 +172,9 @@ pub fn main(socket_path: &PathBuf) -> eyre::Result<()> {
|
|||
Ok(peer) => {
|
||||
let next_id = next_id.clone();
|
||||
let items = items.clone();
|
||||
let last_copied = last_copied.clone();
|
||||
std::thread::spawn(move || {
|
||||
let result = handle_peer(peer, next_id, items);
|
||||
let result = handle_peer(peer, next_id, last_copied, items);
|
||||
if let Err(err) = result {
|
||||
eprintln!("ERROR: Error handling peer: {err:?}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ use crate::MESSAGE_READ;
|
|||
|
||||
use super::MESSAGE_COPY;
|
||||
|
||||
use std::{io::{BufReader, Write}, os::unix::net::UnixStream, path::Path, time::Instant};
|
||||
use std::{
|
||||
io::{BufReader, Write},
|
||||
os::unix::net::UnixStream,
|
||||
path::Path,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use super::Entry;
|
||||
|
||||
|
|
@ -44,14 +49,21 @@ impl eframe::App for App {
|
|||
.show_inside(ui, |ui| {
|
||||
ui.heading("History");
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
for (idx, item) in self.items.iter().enumerate() {
|
||||
let mut frame = egui::Frame::new();
|
||||
let mut frame = egui::Frame::new().inner_margin(3.0);
|
||||
if self.selected_idx == idx {
|
||||
frame = frame.stroke(egui::Stroke::new(1.0, egui::Color32::PURPLE));
|
||||
}
|
||||
frame.show(ui, |ui| match item.mime.as_str() {
|
||||
"text/plain" => {
|
||||
ui.label(str::from_utf8(&item.data).unwrap_or("<invalid UTF-8>"));
|
||||
let mut full =
|
||||
str::from_utf8(&item.data).unwrap_or("<invalid UTF-8>");
|
||||
if full.len() > 1000 {
|
||||
full = &full[..1000];
|
||||
}
|
||||
ui.label(full);
|
||||
}
|
||||
"image/png" => {
|
||||
ui.label("<image>");
|
||||
|
|
@ -60,6 +72,8 @@ impl eframe::App for App {
|
|||
ui.label("<unsupported mime type>");
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -69,6 +83,8 @@ impl eframe::App for App {
|
|||
return;
|
||||
};
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
match item.mime.as_str() {
|
||||
"text/plain" => {
|
||||
ui.label(str::from_utf8(&item.data).unwrap_or("<invalid UTF-8>"));
|
||||
|
|
@ -101,13 +117,15 @@ pub fn main(socket_path: &Path) -> eyre::Result<()> {
|
|||
|
||||
println!("INFO: Reading clipboard history from socket");
|
||||
let start = Instant::now();
|
||||
let items =
|
||||
let mut items: Vec<Entry> =
|
||||
ciborium::from_reader(BufReader::new(socket)).wrap_err("reading items from socket")?;
|
||||
println!(
|
||||
"INFO: Read clipboard history from socket in {:?}",
|
||||
start.elapsed()
|
||||
);
|
||||
|
||||
items.reverse();
|
||||
|
||||
// heh. good design.
|
||||
let socket = UnixStream::connect(&socket_path).wrap_err_with(|| {
|
||||
format!(
|
||||
|
|
|
|||
12
src/main.rs
12
src/main.rs
|
|
@ -1,10 +1,11 @@
|
|||
mod display;
|
||||
mod daemon;
|
||||
mod display;
|
||||
|
||||
use std::{io::Write, os::unix::net::UnixStream};
|
||||
use eyre::{Context, OptionExt, bail};
|
||||
use std::{io::Write, os::unix::net::UnixStream};
|
||||
|
||||
const MAX_ENTRY_SIZE: u64 = 100_000_000;
|
||||
const MAX_ENTRY_SIZE: u64 = 50_000_000;
|
||||
const MAX_HISTORY_BYTE_SIZE: usize = 100_000_000;
|
||||
|
||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||
struct Entry {
|
||||
|
|
@ -38,6 +39,11 @@ fn main() -> eyre::Result<()> {
|
|||
)
|
||||
})?;
|
||||
|
||||
if std::env::args().any(|arg| arg == "--wl-copy") {
|
||||
std::io::copy(&mut std::io::stdin(), &mut std::io::empty())
|
||||
.wrap_err("reading stdin in --wl-copy mode")?;
|
||||
}
|
||||
|
||||
socket
|
||||
.write_all(&[MESSAGE_STORE])
|
||||
.wrap_err("writing request type")?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue