fix: X-owned primary blocks clipboard INCR

Due to an oversight, if `XState` had both a valid primary selection
and a valid clipboard selection, `handle_selection_property_change`
would early return after `check_for_incr` determines the primary selection
existed but was not the recipient of the `PropertyNotifyEvent`, preventing
INCR events from ever reaching the clipboard. An addtion to the
`incr_copy_from_x11` integration test validates this fix.

Also fixed was a missing check in `WaylandSelection`'s `check_for_incr`
to confirm the property requesting more data is indeed the property the
selection is responsible for. I could not figure out how to write an
integration for this mistake, but it is obvious enough in hindsight.
This commit is contained in:
En-En 2025-10-08 17:56:59 +00:00 committed by Supreeeme
parent cdf405fac5
commit d621a0b37a
2 changed files with 51 additions and 20 deletions

View file

@ -146,7 +146,8 @@ impl Selection {
}
fn check_for_incr(&self, event: &x::PropertyNotifyEvent) -> bool {
if event.window() != self.window || event.state() != x::Property::NewValue {
debug_assert_eq!(event.state(), x::Property::NewValue);
if event.window() != self.window {
return false;
}
@ -178,8 +179,18 @@ pub struct WaylandSelection<T: SelectionType> {
}
impl<T: SelectionType> WaylandSelection<T> {
fn check_for_incr(&mut self, connection: &xcb::Connection) -> Option<bool> {
let incr_data = self.incr_data.as_mut()?;
fn check_for_incr(
&mut self,
event: &x::PropertyNotifyEvent,
connection: &xcb::Connection,
) -> bool {
let Some(incr_data) = self.incr_data.as_mut() else {
return false;
};
if incr_data.property != event.atom() {
return false;
}
let incr_end = std::cmp::min(
incr_data.max_req_bytes + incr_data.start,
incr_data.data.len(),
@ -194,7 +205,7 @@ impl<T: SelectionType> WaylandSelection<T> {
}) {
warn!("failed to write selection data: {e:?}");
self.incr_data = None;
return Some(true);
return true;
}
if incr_data.start == incr_end {
@ -210,7 +221,7 @@ impl<T: SelectionType> WaylandSelection<T> {
);
incr_data.start = incr_end;
}
Some(true)
true
}
}
@ -817,6 +828,11 @@ impl XState {
true
}
/// Check if a PropertyNotifyEvent refers to a supported selection property, then make progress
/// on an incremental data transfer if that property is in that process.
/// Returns `true` if an attempt at progressing the data transfer was made, whether or not it
/// succeeded, and `false` otherwise (e.g. the event does not target a selection property or
/// that property is not in the process of an incremental data transfer)
pub(super) fn handle_selection_property_change(
&mut self,
event: &x::PropertyNotifyEvent,
@ -825,24 +841,23 @@ impl XState {
connection: &xcb::Connection,
event: &x::PropertyNotifyEvent,
data: &mut SelectionData<T>,
) -> Option<bool> {
) -> bool {
match event.state() {
x::Property::NewValue => {
if let Some(selection) = &data.x11_selection() {
return Some(selection.check_for_incr(event));
return selection.check_for_incr(event);
}
}
x::Property::Delete => {
if let Some(selection) = data.wayland_selection_mut() {
return selection.check_for_incr(connection);
return selection.check_for_incr(event, connection);
}
}
}
None
false
}
inner(&self.connection, event, &mut self.selection_state.primary)
.or_else(|| inner(&self.connection, event, &mut self.selection_state.clipboard))
.unwrap_or(false)
|| inner(&self.connection, event, &mut self.selection_state.clipboard)
}
}