huge opti
This commit is contained in:
@ -8,6 +8,11 @@ use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use uuid::Uuid;
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::Event;
|
||||
use x11rb::protocol::xfixes::{ConnectionExt as XfixesExt, SelectionEventMask};
|
||||
use x11rb::protocol::xproto::{ConnectionExt as XprotoExt, CreateWindowAux, WindowClass};
|
||||
use x11rb::rust_connection::RustConnection;
|
||||
|
||||
const MAX_IMAGE_PIXELS: usize = 3840 * 2160;
|
||||
|
||||
@ -18,81 +23,128 @@ fn hash_bytes(data: &[u8]) -> u64 {
|
||||
}
|
||||
|
||||
pub fn start(db: Arc<Mutex<Database>>, mut clipboard: Clipboard) -> Result<(), Box<dyn Error>> {
|
||||
println!("Clipboard monitor started (X11 polling mode)...");
|
||||
let (conn, screen_num) =
|
||||
RustConnection::connect(None).map_err(|e| format!("Connexion X11 impossible : {e}"))?;
|
||||
|
||||
let root = conn.setup().roots[screen_num].root;
|
||||
|
||||
let win = conn.generate_id()?;
|
||||
conn.create_window(
|
||||
0,
|
||||
win,
|
||||
root,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
WindowClass::INPUT_ONLY,
|
||||
0,
|
||||
&CreateWindowAux::new(),
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
conn.xfixes_query_version(5, 0)
|
||||
.map_err(|e| format!("Extension XFIXES indisponible : {e}"))?
|
||||
.reply()?;
|
||||
|
||||
let clipboard_atom = conn.intern_atom(false, b"CLIPBOARD")?.reply()?.atom;
|
||||
|
||||
conn.xfixes_select_selection_input(
|
||||
win,
|
||||
clipboard_atom,
|
||||
SelectionEventMask::SET_SELECTION_OWNER,
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
conn.flush()?;
|
||||
println!("Clipboard monitor démarré (X11 XFIXES — zéro polling)");
|
||||
|
||||
let mut last_text: Option<String> = None;
|
||||
let mut last_image_hash: Option<u64> = None;
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
let event = conn.wait_for_event()?;
|
||||
|
||||
match clipboard.get_text() {
|
||||
Ok(raw) => {
|
||||
let text = raw.trim_end_matches('\n').to_string();
|
||||
if text.is_empty() || Some(&text) == last_text.as_ref() {
|
||||
continue;
|
||||
}
|
||||
if let Event::XfixesSelectionNotify(_) = event {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
handle_clipboard_event(&mut clipboard, &db, &mut last_text, &mut last_image_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_text = Some(text.clone());
|
||||
last_image_hash = None;
|
||||
println!("Clipboard update (text)!");
|
||||
fn handle_clipboard_event(
|
||||
clipboard: &mut Clipboard,
|
||||
db: &Arc<Mutex<Database>>,
|
||||
last_text: &mut Option<String>,
|
||||
last_image_hash: &mut Option<u64>,
|
||||
) {
|
||||
match clipboard.get_text() {
|
||||
Ok(raw) => {
|
||||
let text = raw.trim_end_matches('\n').to_string();
|
||||
if text.is_empty() || Some(&text) == last_text.as_ref() {
|
||||
return;
|
||||
}
|
||||
*last_text = Some(text.clone());
|
||||
*last_image_hash = None;
|
||||
println!("Clipboard update (texte)");
|
||||
|
||||
let entry = ClipboardEntry {
|
||||
spawn_db_write(
|
||||
Arc::clone(db),
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Text(text),
|
||||
timestamp: SystemTime::now(),
|
||||
};
|
||||
spawn_db_write(Arc::clone(&db), entry);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Err(_) => {
|
||||
let Ok(img_data) = clipboard.get_image() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let pixel_count = img_data.width * img_data.height;
|
||||
if pixel_count > MAX_IMAGE_PIXELS {
|
||||
eprintln!(
|
||||
"Image ignorée : {}×{} ({} Mpx > limite 4K)",
|
||||
img_data.width,
|
||||
img_data.height,
|
||||
pixel_count / 1_000_000
|
||||
);
|
||||
*last_image_hash = Some(pixel_count as u64);
|
||||
*last_text = None;
|
||||
return;
|
||||
}
|
||||
|
||||
Err(_) => {
|
||||
let Ok(img_data) = clipboard.get_image() else {
|
||||
continue;
|
||||
};
|
||||
let hash = hash_bytes(&img_data.bytes);
|
||||
if Some(hash) == *last_image_hash {
|
||||
return;
|
||||
}
|
||||
*last_image_hash = Some(hash);
|
||||
*last_text = None;
|
||||
println!("Clipboard update (image)");
|
||||
|
||||
let pixel_count = img_data.width * img_data.height;
|
||||
if pixel_count > MAX_IMAGE_PIXELS {
|
||||
eprintln!(
|
||||
"Image ignorée : {}×{} ({} Mpx > limite {}×{})",
|
||||
img_data.width,
|
||||
img_data.height,
|
||||
pixel_count / 1_000_000,
|
||||
3840,
|
||||
2160
|
||||
);
|
||||
last_image_hash = Some(pixel_count as u64);
|
||||
last_text = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
let hash = hash_bytes(&img_data.bytes);
|
||||
if Some(hash) == last_image_hash {
|
||||
continue;
|
||||
}
|
||||
|
||||
last_image_hash = Some(hash);
|
||||
last_text = None;
|
||||
println!("Clipboard update (image)!");
|
||||
|
||||
let entry = ClipboardEntry {
|
||||
content: ClipboardData::Image(Image {
|
||||
spawn_db_write(
|
||||
Arc::clone(db),
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Image(crate::models::Image {
|
||||
raw_pixels: Some(img_data.bytes.into_owned()),
|
||||
width: img_data.width as u32,
|
||||
height: img_data.height as u32,
|
||||
id: Uuid::new_v4(),
|
||||
}),
|
||||
timestamp: SystemTime::now(),
|
||||
};
|
||||
spawn_db_write(Arc::clone(&db), entry);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_db_write(db: Arc<Mutex<Database>>, entry: ClipboardEntry) {
|
||||
thread::spawn(move || {
|
||||
let db_lock = db.lock().unwrap();
|
||||
if let Err(e) = db_lock.append(entry) {
|
||||
eprintln!("SQLite writing error: {}", e);
|
||||
let lock = db.lock().unwrap();
|
||||
if let Err(e) = lock.append(entry) {
|
||||
eprintln!("SQLite write error: {e}");
|
||||
} else {
|
||||
println!("SQLite updated!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user