diff --git a/src/xstate/selection.rs b/src/xstate/selection.rs index a340cfb..2f8f9c5 100644 --- a/src/xstate/selection.rs +++ b/src/xstate/selection.rs @@ -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 { } impl WaylandSelection { - fn check_for_incr(&mut self, connection: &xcb::Connection) -> Option { - 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 WaylandSelection { }) { 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 WaylandSelection { ); 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, - ) -> Option { + ) -> 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) } } diff --git a/tests/integration.rs b/tests/integration.rs index b56eb64..1fe3eaa 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -332,6 +332,7 @@ xcb::atoms_struct! { skip_taskbar => b"_NET_WM_STATE_SKIP_TASKBAR", transient_for => b"WM_TRANSIENT_FOR", clipboard => b"CLIPBOARD", + primary => b"PRIMARY", targets => b"TARGETS", multiple => b"MULTIPLE", wm_state => b"WM_STATE", @@ -508,16 +509,14 @@ impl Connection { } #[track_caller] - fn set_selection_owner(&self, window: x::Window) { + fn set_selection_owner(&self, window: x::Window, selection: x::Atom) { self.send_and_check_request(&x::SetSelectionOwner { owner: window, - selection: self.atoms.clipboard, + selection, time: x::CURRENT_TIME, }) .unwrap(); - let owner = self.get_reply(&x::GetSelectionOwner { - selection: self.atoms.clipboard, - }); + let owner = self.get_reply(&x::GetSelectionOwner { selection }); assert_eq!(window, owner.owner(), "Unexpected selection owner"); } @@ -993,7 +992,7 @@ fn copy_from_x11() { let window = connection.new_window(connection.root, 0, 0, 20, 20, false); f.map_as_toplevel(&mut connection, window); - connection.set_selection_owner(window); + connection.set_selection_owner(window, connection.atoms.clipboard); let request = connection.await_selection_request(); assert_eq!(request.target(), connection.atoms.targets); @@ -1347,7 +1346,7 @@ fn bad_clipboard_data() { let mut connection = Connection::new(&f.display); let window = connection.new_window(connection.root, 0, 0, 20, 20, false); f.map_as_toplevel(&mut connection, window); - connection.set_selection_owner(window); + connection.set_selection_owner(window, connection.atoms.clipboard); let request = connection.await_selection_request(); assert_eq!(request.target(), connection.atoms.targets); @@ -1503,9 +1502,10 @@ fn incr_copy_from_x11() { let window = connection.new_window(connection.root, 0, 0, 20, 20, false); f.map_as_toplevel(&mut connection, window); - connection.set_selection_owner(window); + connection.set_selection_owner(window, connection.atoms.clipboard); let request = connection.await_selection_request(); assert_eq!(request.target(), connection.atoms.targets); + assert_eq!(request.selection(), connection.atoms.clipboard); connection.set_property( request.requestor(), x::ATOM_ATOM, @@ -1515,6 +1515,22 @@ fn incr_copy_from_x11() { connection.send_selection_notify(&request); f.wait_and_dispatch(); + // Also give the window the primary selection. + // Due to a bug introduced in primary selection support, `XState::selection_state` having both + // a primary and clipboard X selection prevented clipboard INCR checks from occuring. + connection.set_selection_owner(window, connection.atoms.primary); + let request = connection.await_selection_request(); + assert_eq!(request.target(), connection.atoms.targets); + assert_eq!(request.selection(), connection.atoms.primary); + connection.set_property( + request.requestor(), + x::ATOM_ATOM, + request.property(), + &[connection.atoms.targets, connection.atoms.mime2], + ); + connection.send_selection_notify(&request); + f.wait_and_dispatch(); + let mut destination_property = x::Atom::none(); let mut begin_incr = Some(|connection: &mut Connection| { let request = connection.await_selection_request(); @@ -1611,7 +1627,7 @@ fn wayland_then_x11_clipboard_owner() { connection.verify_clipboard_owner(connection.wm_window); connection.get_selection_owner_change_events(false, window); - connection.set_selection_owner(window); + connection.set_selection_owner(window, connection.atoms.clipboard); f.testwl.dispatch(); connection.verify_clipboard_owner(window);