feat: send huge Wayland-to-X selections via INCR
Since the connection handshake establishes the most data a request can send, if the data length exceeds that limit, we can follow ICCCM 2.7.2 sending the INCR property and continuing to send data via PropertyNotify events. To test the changes, we create `XState::set_max_req_bytes` to forcefully trigger the INCR mechanism in integration test runs with a constant, substantially less amount of data.
This commit is contained in:
parent
334933b212
commit
1ec45141e6
4 changed files with 254 additions and 81 deletions
|
|
@ -82,6 +82,10 @@ impl xwls::RunData for TestData {
|
|||
assert!(server.is_some());
|
||||
server.take()
|
||||
}
|
||||
|
||||
fn max_req_len_bytes(&self) -> Option<usize> {
|
||||
Some(500)
|
||||
}
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
|
|
@ -551,6 +555,23 @@ impl Connection {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn await_property_notify(&mut self) -> x::PropertyNotifyEvent {
|
||||
match self.await_event() {
|
||||
xcb::Event::X(x::Event::PropertyNotify(r)) => r,
|
||||
other => panic!("Didn't get property notify event, instead got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn get_property_change_events(&self, window: x::Window) {
|
||||
self.send_and_check_request(&x::ChangeWindowAttributes {
|
||||
window,
|
||||
value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)],
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn verify_clipboard_owner(&self, window: x::Window) {
|
||||
let owner = self.get_reply(&x::GetSelectionOwner {
|
||||
|
|
@ -1163,17 +1184,14 @@ fn copy_from_wayland() {
|
|||
|
||||
#[test]
|
||||
fn incr_copy_from_wayland() {
|
||||
// After a little binary searching, the test passes on less than 16,777,184 bytes, fails due to
|
||||
// a failed assert on matching `dest1_atom` starting at 16,777,185 bytes, and starts crashing
|
||||
// instead at 16,777,189 bytes.
|
||||
const BYTES: usize = 16_777_189;
|
||||
const BYTES: usize = 3000;
|
||||
let mut f = Fixture::new();
|
||||
let mut connection = Connection::new(&f.display);
|
||||
|
||||
let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
|
||||
connection.get_selection_owner_change_events(true, window);
|
||||
f.map_as_toplevel(&mut connection, window);
|
||||
let offer = vec![testwl::PasteData {
|
||||
let mut offer = vec![testwl::PasteData {
|
||||
mime_type: "text/plain".into(),
|
||||
data: std::iter::successors(Some(0u8), |n| Some(n.wrapping_add(1)))
|
||||
.take(BYTES)
|
||||
|
|
@ -1219,49 +1237,70 @@ fn incr_copy_from_wayland() {
|
|||
let targets: &[x::Atom] = reply.value();
|
||||
assert_eq!(targets.len(), 1);
|
||||
|
||||
for testwl::PasteData { mime_type, data } in offer {
|
||||
let atom = connection
|
||||
.get_reply(&x::InternAtom {
|
||||
only_if_exists: true,
|
||||
name: mime_type.as_bytes(),
|
||||
})
|
||||
.atom();
|
||||
assert_ne!(atom, x::ATOM_NONE);
|
||||
assert!(targets.contains(&atom));
|
||||
let offer_data = offer.pop().unwrap();
|
||||
let (mime_type, data) = (offer_data.mime_type, offer_data.data);
|
||||
let atom = connection
|
||||
.get_reply(&x::InternAtom {
|
||||
only_if_exists: true,
|
||||
name: mime_type.as_bytes(),
|
||||
})
|
||||
.atom();
|
||||
assert_ne!(atom, x::ATOM_NONE);
|
||||
assert!(targets.contains(&atom));
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
connection
|
||||
.send_and_check_request(&x::ConvertSelection {
|
||||
requestor: window,
|
||||
selection: connection.atoms.clipboard,
|
||||
target: atom,
|
||||
property: dest1_atom,
|
||||
time: x::CURRENT_TIME,
|
||||
})
|
||||
.unwrap();
|
||||
connection
|
||||
.send_and_check_request(&x::ConvertSelection {
|
||||
requestor: window,
|
||||
selection: connection.atoms.clipboard,
|
||||
target: atom,
|
||||
property: dest1_atom,
|
||||
time: x::CURRENT_TIME,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
f.wait_and_dispatch();
|
||||
let request = connection.await_selection_notify();
|
||||
assert_eq!(request.requestor(), window);
|
||||
assert_eq!(request.selection(), connection.atoms.clipboard);
|
||||
assert_eq!(request.target(), atom);
|
||||
// The particular assert which fails in the narrow byte range as mentioned above.
|
||||
assert_eq!(request.property(), dest1_atom);
|
||||
f.wait_and_dispatch();
|
||||
let request = connection.await_selection_notify();
|
||||
assert_eq!(request.requestor(), window);
|
||||
assert_eq!(request.selection(), connection.atoms.clipboard);
|
||||
assert_eq!(request.target(), atom);
|
||||
assert_eq!(request.property(), dest1_atom);
|
||||
|
||||
let val: Vec<u8> = connection
|
||||
.get_reply(&x::GetProperty {
|
||||
delete: true,
|
||||
window,
|
||||
property: dest1_atom,
|
||||
r#type: x::ATOM_ANY,
|
||||
long_offset: 0,
|
||||
long_length: (BYTES / 4 + BYTES % 4) as u32,
|
||||
})
|
||||
.value()
|
||||
.to_vec();
|
||||
connection.get_property_change_events(window);
|
||||
let reply = connection.get_reply(&x::GetProperty {
|
||||
delete: true,
|
||||
window,
|
||||
property: dest1_atom,
|
||||
r#type: x::ATOM_ANY,
|
||||
long_offset: 0,
|
||||
long_length: (BYTES / 4 + BYTES % 4) as u32,
|
||||
});
|
||||
assert_eq!(reply.r#type(), connection.atoms.incr);
|
||||
assert_eq!(reply.value::<u32>().len(), 1);
|
||||
assert!(reply.value::<u32>()[0] <= data.len() as u32);
|
||||
|
||||
assert_eq!(val.len(), data.len());
|
||||
assert_eq!(val, data);
|
||||
let delete_property = connection.await_property_notify();
|
||||
assert_eq!(delete_property.state(), x::Property::Delete);
|
||||
assert_eq!(delete_property.atom(), dest1_atom);
|
||||
|
||||
for (idx, chunk) in data.chunks(500).chain([]).enumerate() {
|
||||
let new_property = connection.await_property_notify();
|
||||
assert_eq!(new_property.state(), x::Property::NewValue, "chunk {idx}");
|
||||
assert_eq!(new_property.atom(), dest1_atom, "chunk {idx}");
|
||||
|
||||
let incr_reply = connection.get_reply(&x::GetProperty {
|
||||
delete: true,
|
||||
window,
|
||||
property: dest1_atom,
|
||||
r#type: x::ATOM_ANY,
|
||||
long_offset: 0,
|
||||
long_length: (BYTES / 4 + BYTES % 4) as u32,
|
||||
});
|
||||
assert_eq!(incr_reply.r#type(), atom, "chunk {idx}");
|
||||
assert_eq!(incr_reply.value::<u8>(), chunk, "chunk {idx}");
|
||||
|
||||
let delete_property = connection.await_property_notify();
|
||||
assert_eq!(delete_property.state(), x::Property::Delete, "chunk {idx}");
|
||||
assert_eq!(delete_property.atom(), dest1_atom, "chunk {idx}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue