Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion crates/clipboard/src/listener/x11/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,13 @@ impl Context {
Ok(())
}

pub fn get_available_formats(&self) -> Result<Vec<String>, Error> {
/// Get available clipboard formats. Any `XfixesSelectionNotify` events
/// encountered while waiting for the `SelectionNotify` reply are collected
/// into `pending_events` so the caller can re-process them.
pub fn get_available_formats(
&self,
pending_events: &mut Vec<x11rb::protocol::Event>,
) -> Result<Vec<String>, Error> {
drop(
self.connection
.delete_property(self.window, self.atom_cache.clipcat_clipboard)
Expand Down Expand Up @@ -169,6 +175,10 @@ impl Context {
}
return Ok(formats);
}

// Preserve any other events (especially XfixesSelectionNotify)
// so they are not lost.
pending_events.push(event);
}
Ok(Vec::new())
}
Expand Down
65 changes: 41 additions & 24 deletions crates/clipboard/src/listener/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ fn build_thread(
)
.context(error::RegisterIoResourceSnafu)?;

let mut pending_events: Vec<X11Event> = Vec::new();

while is_running.load(Ordering::Relaxed) {
tracing::trace!("Wait for readiness events");

Expand All @@ -116,31 +118,22 @@ fn build_thread(
);
}

for event in &events {
if event.token() == CONTEXT_TOKEN {
let has_context_event = events.iter().any(|event| event.token() == CONTEXT_TOKEN);
if !has_context_event && pending_events.is_empty() {
continue;
}

// Drain all buffered X11 events from the connection.
// mio only signals fd readability once; x11rb may buffer
// multiple events internally from a single read.
loop {
// First process any events saved from get_available_formats()
let x11_event = if let Some(evt) = pending_events.pop() {
evt
} else {
match context.poll_for_event() {
Ok(X11Event::XfixesSelectionNotify(_event)) => {
match context.get_available_formats() {
Ok(mut formats) => {
// filter sensitive content
if clip_filter.filter_sensitive_mime_type(formats.iter()) {
tracing::info!("Sensitive content detected, ignore it");
continue;
}

if let Some(mime) = extract_mime(&mut formats) {
notifier.notify_all(mime);
}
}
Err(err) => {
tracing::warn!(
"Clipboard is changed but we could not get available \
formats, error: {err}"
);
}
}
}
Ok(_) | Err(Error::NoEvent) => {}
Ok(evt) => evt,
Err(Error::NoEvent) => break,
Err(err) => {
tracing::warn!("{err}, try to re-connect");
if let Err(err) = try_reconnect(
Expand All @@ -159,9 +152,33 @@ fn build_thread(
&context.display_name(),
);
}
break;
}
}
};

if let X11Event::XfixesSelectionNotify(_) = x11_event {
match context.get_available_formats(&mut pending_events) {
Ok(mut formats) => {
// filter sensitive content
if clip_filter.filter_sensitive_mime_type(formats.iter()) {
tracing::info!("Sensitive content detected, ignore it");
continue;
}

if let Some(mime) = extract_mime(&mut formats) {
notifier.notify_all(mime);
}
}
Err(err) => {
tracing::warn!(
"Clipboard is changed but we could not get available formats, \
error: {err}"
);
}
}
}
// Other event types are intentionally ignored
}
}

Expand Down