Files
rklip/rklipd/src/ws/x11.rs
2026-05-21 09:54:09 +02:00

148 lines
4.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::models::{ClipboardData, ClipboardEntry, Image};
use arboard::Clipboard;
use std::collections::hash_map::DefaultHasher;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::sync::mpsc;
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;
fn hash_bytes(data: &[u8]) -> u64 {
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
hasher.finish()
}
pub fn start(
tx: mpsc::Sender<ClipboardEntry>,
mut clipboard: Clipboard,
) -> Result<(), Box<dyn Error>> {
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 {
let event = conn.wait_for_event()?;
if let Event::XfixesSelectionNotify(_) = event {
thread::sleep(Duration::from_millis(50));
handle_clipboard_event(&mut clipboard, &tx, &mut last_text, &mut last_image_hash);
}
}
}
fn handle_clipboard_event(
clipboard: &mut Clipboard,
tx: &mpsc::Sender<ClipboardEntry>,
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)");
if tx
.send(ClipboardEntry {
content: ClipboardData::Text(text),
timestamp: SystemTime::now(),
})
.is_err()
{
eprintln!("X11 : writer thread disparu");
}
}
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
);
let sentinel_hash = hash_bytes(&img_data.bytes[..img_data.bytes.len().min(256)]);
*last_image_hash = Some(sentinel_hash);
*last_text = None;
return;
}
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)");
if tx
.send(ClipboardEntry {
content: ClipboardData::Image(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(),
})
.is_err()
{
eprintln!("X11 : writer thread disparu");
}
}
}
}