fix: uniquely define property separate from target
Both PRIMARY and CLIPBOARD were using the same target atoms for their property. When both are simultaneously requested (the behavior of `wl-clip-persist --clipboard both`), the first would delete the property the second was prepared to write data to, resulting in getting the GetProperty reply containing the failure data. This commit distinguishes `target` as the atom of a mime type contained within `TARGETS`, and `property` as the atom which contains both data of `target` and which selection requested it.
This commit is contained in:
parent
e991cb39c2
commit
114d48e2e1
1 changed files with 81 additions and 69 deletions
|
|
@ -11,12 +11,14 @@ use xcb::x;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SelectionTargetId {
|
struct SelectionTargetId {
|
||||||
name: String,
|
name: String,
|
||||||
atom: x::Atom,
|
target: x::Atom,
|
||||||
|
property: x::Atom,
|
||||||
source: Option<String>,
|
source: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PendingSelectionData {
|
struct PendingSelectionData {
|
||||||
target: x::Atom,
|
target: x::Atom,
|
||||||
|
property: x::Atom,
|
||||||
pipe: WritePipe,
|
pipe: WritePipe,
|
||||||
incr: bool,
|
incr: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -41,14 +43,15 @@ impl X11Selection for Selection {
|
||||||
|
|
||||||
fn write_to(&self, mime: &str, pipe: WritePipe) {
|
fn write_to(&self, mime: &str, pipe: WritePipe) {
|
||||||
if let Some(target) = self.mimes.iter().find(|target| target.name == mime) {
|
if let Some(target) = self.mimes.iter().find(|target| target.name == mime) {
|
||||||
// We use the target as the property to write to
|
// A concatenation of the target and the selection type are used to create a distinct
|
||||||
|
// property to write to (see XState::intern_target_property_atoms).
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.connection
|
.connection
|
||||||
.send_and_check_request(&x::ConvertSelection {
|
.send_and_check_request(&x::ConvertSelection {
|
||||||
requestor: self.window,
|
requestor: self.window,
|
||||||
selection: self.selection,
|
selection: self.selection,
|
||||||
target: target.atom,
|
target: target.target,
|
||||||
property: target.atom,
|
property: target.property,
|
||||||
time: self.selection_time,
|
time: self.selection_time,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
|
@ -57,7 +60,8 @@ impl X11Selection for Selection {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pending.borrow_mut().push(PendingSelectionData {
|
self.pending.borrow_mut().push(PendingSelectionData {
|
||||||
target: target.atom,
|
target: target.target,
|
||||||
|
property: target.property,
|
||||||
pipe,
|
pipe,
|
||||||
incr: false,
|
incr: false,
|
||||||
})
|
})
|
||||||
|
|
@ -82,8 +86,17 @@ impl Selection {
|
||||||
mut pipe,
|
mut pipe,
|
||||||
incr,
|
incr,
|
||||||
target,
|
target,
|
||||||
|
property,
|
||||||
} = pending.swap_remove(idx);
|
} = pending.swap_remove(idx);
|
||||||
let reply = match get_property_any(&self.connection, self.window, target) {
|
let request = self.connection.send_request(&x::GetProperty {
|
||||||
|
delete: true,
|
||||||
|
window: self.window,
|
||||||
|
property,
|
||||||
|
r#type: x::ATOM_ANY,
|
||||||
|
long_offset: 0,
|
||||||
|
long_length: u32::MAX,
|
||||||
|
});
|
||||||
|
let reply = match self.connection.wait_for_reply(request) {
|
||||||
Ok(reply) => reply,
|
Ok(reply) => reply,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(
|
warn!(
|
||||||
|
|
@ -97,16 +110,17 @@ impl Selection {
|
||||||
debug!(
|
debug!(
|
||||||
"got type {} for mime type {}",
|
"got type {} for mime type {}",
|
||||||
get_atom_name(&self.connection, reply.r#type()),
|
get_atom_name(&self.connection, reply.r#type()),
|
||||||
get_atom_name(&self.connection, target)
|
get_atom_name(&self.connection, target),
|
||||||
);
|
);
|
||||||
|
|
||||||
if reply.r#type() == self.incr {
|
if reply.r#type() == self.incr {
|
||||||
debug!(
|
debug!(
|
||||||
"beginning incr for {}",
|
"beginning incr for {}",
|
||||||
get_atom_name(&self.connection, target)
|
get_atom_name(&self.connection, property)
|
||||||
);
|
);
|
||||||
pending.push(PendingSelectionData {
|
pending.push(PendingSelectionData {
|
||||||
target,
|
target,
|
||||||
|
property,
|
||||||
pipe,
|
pipe,
|
||||||
incr: true,
|
incr: true,
|
||||||
});
|
});
|
||||||
|
|
@ -132,6 +146,7 @@ impl Selection {
|
||||||
);
|
);
|
||||||
pending.push(PendingSelectionData {
|
pending.push(PendingSelectionData {
|
||||||
target,
|
target,
|
||||||
|
property,
|
||||||
pipe,
|
pipe,
|
||||||
incr: true,
|
incr: true,
|
||||||
})
|
})
|
||||||
|
|
@ -152,7 +167,7 @@ impl Selection {
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = self.pending.borrow().iter().find_map(|pending| {
|
let target = self.pending.borrow().iter().find_map(|pending| {
|
||||||
(pending.target == event.atom() && pending.incr).then_some(pending.target)
|
(pending.property == event.atom() && pending.incr).then_some(pending.target)
|
||||||
});
|
});
|
||||||
if let Some(target) = target {
|
if let Some(target) = target {
|
||||||
self.handle_notify(target);
|
self.handle_notify(target);
|
||||||
|
|
@ -416,14 +431,26 @@ impl<T: SelectionType> SelectionDataImpl for SelectionData<T> {
|
||||||
debug!("got targets: {targets_str:?}");
|
debug!("got targets: {targets_str:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selection = get_atom_name(connection, self.atom);
|
||||||
let mimes = targets
|
let mimes = targets
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|atom| ![atoms.targets, atoms.multiple, atoms.save_targets].contains(atom))
|
.filter(|atom| ![atoms.targets, atoms.multiple, atoms.save_targets].contains(atom))
|
||||||
.map(|target_atom| SelectionTargetId {
|
.map(|target| {
|
||||||
name: get_atom_name(connection, target_atom),
|
let name = get_atom_name(connection, target);
|
||||||
atom: target_atom,
|
let property = connection
|
||||||
source: None,
|
.wait_for_reply(connection.send_request(&x::InternAtom {
|
||||||
|
only_if_exists: false,
|
||||||
|
name: &[name.as_bytes(), b"_", selection.as_bytes()].concat(),
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
.atom();
|
||||||
|
SelectionTargetId {
|
||||||
|
name,
|
||||||
|
target,
|
||||||
|
property,
|
||||||
|
source: None,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -469,7 +496,7 @@ impl<T: SelectionType> SelectionDataImpl for SelectionData<T> {
|
||||||
|
|
||||||
let req_target = request.target();
|
let req_target = request.target();
|
||||||
if req_target == atoms.targets {
|
if req_target == atoms.targets {
|
||||||
let atoms: Box<[x::Atom]> = mimes.iter().map(|t| t.atom).collect();
|
let atoms: Box<[x::Atom]> = mimes.iter().map(|t| t.target).collect();
|
||||||
|
|
||||||
match connection.send_and_check_request(&x::ChangeProperty {
|
match connection.send_and_check_request(&x::ChangeProperty {
|
||||||
mode: x::PropMode::Replace,
|
mode: x::PropMode::Replace,
|
||||||
|
|
@ -485,7 +512,7 @@ impl<T: SelectionType> SelectionDataImpl for SelectionData<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let Some(target) = mimes.iter().find(|t| t.atom == req_target) else {
|
let Some(target) = mimes.iter().find(|t| t.target == req_target) else {
|
||||||
if log::log_enabled!(log::Level::Debug) {
|
if log::log_enabled!(log::Level::Debug) {
|
||||||
let name = get_atom_name(connection, req_target);
|
let name = get_atom_name(connection, req_target);
|
||||||
debug!(
|
debug!(
|
||||||
|
|
@ -521,14 +548,14 @@ impl<T: SelectionType> SelectionDataImpl for SelectionData<T> {
|
||||||
}
|
}
|
||||||
debug!(
|
debug!(
|
||||||
"beginning incr for {}",
|
"beginning incr for {}",
|
||||||
get_atom_name(connection, target.atom)
|
get_atom_name(connection, target.target)
|
||||||
);
|
);
|
||||||
*incr_data = Some(WaylandIncrInfo {
|
*incr_data = Some(WaylandIncrInfo {
|
||||||
data,
|
data,
|
||||||
start: 0,
|
start: 0,
|
||||||
target_window: request.requestor(),
|
target_window: request.requestor(),
|
||||||
property: request.property(),
|
property: request.property(),
|
||||||
target_type: target.atom,
|
target_type: target.target,
|
||||||
max_req_bytes,
|
max_req_bytes,
|
||||||
});
|
});
|
||||||
true
|
true
|
||||||
|
|
@ -537,7 +564,7 @@ impl<T: SelectionType> SelectionDataImpl for SelectionData<T> {
|
||||||
mode: x::PropMode::Replace,
|
mode: x::PropMode::Replace,
|
||||||
window: request.requestor(),
|
window: request.requestor(),
|
||||||
property: request.property(),
|
property: request.property(),
|
||||||
r#type: target.atom,
|
r#type: target.target,
|
||||||
data: &data,
|
data: &data,
|
||||||
}) {
|
}) {
|
||||||
Ok(_) => true,
|
Ok(_) => true,
|
||||||
|
|
@ -585,6 +612,26 @@ impl SelectionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XState {
|
impl XState {
|
||||||
|
fn intern_target_property_atoms(&self, mime: &[u8], suffix: &[u8]) -> (x::Atom, x::Atom) {
|
||||||
|
let target = self
|
||||||
|
.connection
|
||||||
|
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
||||||
|
only_if_exists: false,
|
||||||
|
name: mime,
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
.atom();
|
||||||
|
let property = self
|
||||||
|
.connection
|
||||||
|
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
||||||
|
only_if_exists: false,
|
||||||
|
name: &[mime, suffix].concat(),
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
.atom();
|
||||||
|
(target, property)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection<Clipboard>) {
|
pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection<Clipboard>) {
|
||||||
let mut utf8_xwl = false;
|
let mut utf8_xwl = false;
|
||||||
let mut utf8_wl = false;
|
let mut utf8_wl = false;
|
||||||
|
|
@ -598,17 +645,12 @@ impl XState {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let atom = self
|
let (target, property) =
|
||||||
.connection
|
self.intern_target_property_atoms(mime.as_bytes(), b"_CLIPBOARD");
|
||||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
|
||||||
only_if_exists: false,
|
|
||||||
name: mime.as_bytes(),
|
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
SelectionTargetId {
|
SelectionTargetId {
|
||||||
name: mime.clone(),
|
name: mime.clone(),
|
||||||
atom: atom.atom(),
|
target,
|
||||||
|
property,
|
||||||
source: None,
|
source: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -616,17 +658,12 @@ impl XState {
|
||||||
|
|
||||||
if utf8_wl && !utf8_xwl {
|
if utf8_wl && !utf8_xwl {
|
||||||
let name = "UTF8_STRING".to_string();
|
let name = "UTF8_STRING".to_string();
|
||||||
let atom = self
|
let (target, property) =
|
||||||
.connection
|
self.intern_target_property_atoms(name.as_bytes(), b"_CLIPBOARD");
|
||||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
|
||||||
only_if_exists: false,
|
|
||||||
name: name.as_bytes(),
|
|
||||||
}))
|
|
||||||
.unwrap()
|
|
||||||
.atom();
|
|
||||||
mimes.push(SelectionTargetId {
|
mimes.push(SelectionTargetId {
|
||||||
name,
|
name,
|
||||||
atom,
|
target,
|
||||||
|
property,
|
||||||
source: Some("text/plain;charset=utf-8".to_string()),
|
source: Some("text/plain;charset=utf-8".to_string()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -660,17 +697,12 @@ impl XState {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let atom = self
|
let (target, property) =
|
||||||
.connection
|
self.intern_target_property_atoms(mime.as_bytes(), b"_PRIMARY");
|
||||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
|
||||||
only_if_exists: false,
|
|
||||||
name: mime.as_bytes(),
|
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
SelectionTargetId {
|
SelectionTargetId {
|
||||||
name: mime.clone(),
|
name: mime.clone(),
|
||||||
atom: atom.atom(),
|
target,
|
||||||
|
property,
|
||||||
source: None,
|
source: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -678,17 +710,12 @@ impl XState {
|
||||||
|
|
||||||
if utf8_wl && !utf8_xwl {
|
if utf8_wl && !utf8_xwl {
|
||||||
let name = "UTF8_STRING".to_string();
|
let name = "UTF8_STRING".to_string();
|
||||||
let atom = self
|
let (target, property) =
|
||||||
.connection
|
self.intern_target_property_atoms(name.as_bytes(), b"_PRIMARY");
|
||||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
|
||||||
only_if_exists: false,
|
|
||||||
name: name.as_bytes(),
|
|
||||||
}))
|
|
||||||
.unwrap()
|
|
||||||
.atom();
|
|
||||||
mimes.push(SelectionTargetId {
|
mimes.push(SelectionTargetId {
|
||||||
name,
|
name,
|
||||||
atom,
|
target,
|
||||||
|
property,
|
||||||
source: Some("text/plain;charset=utf-8".to_string()),
|
source: Some("text/plain;charset=utf-8".to_string()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -889,18 +916,3 @@ impl XState {
|
||||||
|| inner(&self.connection, event, &mut self.selection_state.clipboard)
|
|| inner(&self.connection, event, &mut self.selection_state.clipboard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_property_any(
|
|
||||||
connection: &xcb::Connection,
|
|
||||||
window: x::Window,
|
|
||||||
property: x::Atom,
|
|
||||||
) -> xcb::Result<x::GetPropertyReply> {
|
|
||||||
connection.wait_for_reply(connection.send_request(&x::GetProperty {
|
|
||||||
delete: true,
|
|
||||||
window,
|
|
||||||
property,
|
|
||||||
r#type: x::ATOM_ANY,
|
|
||||||
long_offset: 0,
|
|
||||||
long_length: u32::MAX,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue