xstate: always advertise utf8 mime type as UTF8_STRING from Wayland selection

Fixes #87
This commit is contained in:
Shawn Wallace 2025-01-10 00:36:02 -05:00
parent ba9c1a6a3e
commit 8f55e27f63
3 changed files with 92 additions and 3 deletions

View file

@ -12,6 +12,7 @@ use xcb::x;
struct SelectionTargetId { struct SelectionTargetId {
name: String, name: String,
atom: x::Atom, atom: x::Atom,
source: Option<String>,
} }
struct PendingSelectionData { struct PendingSelectionData {
@ -227,10 +228,18 @@ impl XState {
} }
pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection) { pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection) {
let mimes = selection let mut utf8_xwl = false;
let mut utf8_wl = false;
let mut mimes: Vec<SelectionTargetId> = selection
.mime_types .mime_types
.iter() .iter()
.map(|mime| { .map(|mime| {
match mime.as_str() {
"UTF8_STRING" => utf8_xwl = true,
"text/plain;charset=utf-8" => utf8_wl = true,
_ => {}
}
let atom = self let atom = self
.connection .connection
.wait_for_reply(self.connection.send_request(&x::InternAtom { .wait_for_reply(self.connection.send_request(&x::InternAtom {
@ -242,10 +251,28 @@ impl XState {
SelectionTargetId { SelectionTargetId {
name: mime.clone(), name: mime.clone(),
atom: atom.atom(), atom: atom.atom(),
source: None,
} }
}) })
.collect(); .collect();
if utf8_wl && !utf8_xwl {
let name = "UTF8_STRING".to_string();
let atom = self
.connection
.wait_for_reply(self.connection.send_request(&x::InternAtom {
only_if_exists: false,
name: name.as_bytes(),
}))
.unwrap()
.atom();
mimes.push(SelectionTargetId {
name,
atom,
source: Some("text/plain;charset=utf-8".to_string()),
});
}
self.selection_data.current_selection = Some(CurrentSelection::Wayland { self.selection_data.current_selection = Some(CurrentSelection::Wayland {
mimes, mimes,
inner: selection, inner: selection,
@ -364,7 +391,12 @@ impl XState {
return true; return true;
}; };
let data = inner.receive(target.name.clone(), server_state); let mime_name = target
.source
.as_ref()
.cloned()
.unwrap_or_else(|| target.name.clone());
let data = inner.receive(mime_name, server_state);
match self.connection.send_and_check_request(&x::ChangeProperty { match self.connection.send_and_check_request(&x::ChangeProperty {
mode: x::PropMode::Replace, mode: x::PropMode::Replace,
window: e.requestor(), window: e.requestor(),
@ -479,6 +511,7 @@ impl XState {
.map(|target_atom| SelectionTargetId { .map(|target_atom| SelectionTargetId {
name: get_atom_name(&self.connection, target_atom), name: get_atom_name(&self.connection, target_atom),
atom: target_atom, atom: target_atom,
source: None,
}) })
.collect(); .collect();

View file

@ -1306,3 +1306,59 @@ fn wayland_then_x11_clipboard_owner() {
assert_eq!(request.selection(), connection.atoms.clipboard); assert_eq!(request.selection(), connection.atoms.clipboard);
assert_eq!(request.target(), connection.atoms.targets); assert_eq!(request.target(), connection.atoms.targets);
} }
#[test]
fn fake_selection_targets() {
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);
let data = b"boingloings";
f.map_as_toplevel(&mut connection, window);
let offer = vec![testwl::PasteData {
mime_type: "text/plain;charset=utf-8".into(),
data: data.to_vec(),
}];
f.testwl.create_data_offer(offer.clone());
connection.await_selection_owner_change();
connection.verify_clipboard_owner(connection.wm_window);
connection.get_selection_owner_change_events(false, window);
let utf8_string = connection
.get_reply(&x::InternAtom {
only_if_exists: false,
name: b"UTF8_STRING",
})
.atom();
connection
.send_and_check_request(&x::ConvertSelection {
requestor: window,
selection: connection.atoms.clipboard,
target: utf8_string,
property: utf8_string,
time: x::CURRENT_TIME,
})
.unwrap();
f.wait_and_dispatch();
let notify = connection.await_selection_notify();
assert_eq!(notify.property(), utf8_string, "ConvertSelection failed");
let reply = connection.get_reply(&x::GetProperty {
delete: false,
window,
property: utf8_string,
r#type: utf8_string,
long_offset: 0,
long_length: data.len() as u32,
});
let paste_data: &[u8] = reply.value();
assert_eq!(
std::str::from_utf8(paste_data).unwrap(),
std::str::from_utf8(data).unwrap()
);
}

View file

@ -875,7 +875,7 @@ impl Dispatch<WlDataOffer, Vec<PasteData>> for State {
let pos = data let pos = data
.iter() .iter()
.position(|data| data.mime_type == mime_type) .position(|data| data.mime_type == mime_type)
.expect("Invalid mime type: {mime_type}"); .unwrap_or_else(|| panic!("Invalid mime type: {mime_type}"));
let mut stream = UnixStream::from(fd); let mut stream = UnixStream::from(fd);
stream.write_all(&data[pos].data).unwrap(); stream.write_all(&data[pos].data).unwrap();