diff --git a/crates/clipboard/src/listener/x11/context.rs b/crates/clipboard/src/listener/x11/context.rs index eb6b826b..800febb2 100644 --- a/crates/clipboard/src/listener/x11/context.rs +++ b/crates/clipboard/src/listener/x11/context.rs @@ -110,7 +110,13 @@ impl Context { Ok(()) } - pub fn get_available_formats(&self) -> Result, 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, + ) -> Result, Error> { drop( self.connection .delete_property(self.window, self.atom_cache.clipcat_clipboard) @@ -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()) } diff --git a/crates/clipboard/src/listener/x11/mod.rs b/crates/clipboard/src/listener/x11/mod.rs index 7da47678..ba728053 100644 --- a/crates/clipboard/src/listener/x11/mod.rs +++ b/crates/clipboard/src/listener/x11/mod.rs @@ -104,6 +104,8 @@ fn build_thread( ) .context(error::RegisterIoResourceSnafu)?; + let mut pending_events: Vec = Vec::new(); + while is_running.load(Ordering::Relaxed) { tracing::trace!("Wait for readiness events"); @@ -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( @@ -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 } }