use std::os::fd::{AsRawFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use raw_window_handle::RawWindowHandle;
use teksilo_canvas::Point;
use teksilo_core::raw_handle::ParentHandle;
use teksilo_core::window::TeksiloWindowId;
use teksilo_core::{
AppEventPoster, DragImageData, DropOutcome, ExternalDropData, OutboundDragData,
};
use x11rb::connection::Connection as _;
use x11rb::protocol::Event;
use x11rb::protocol::xproto::{
Atom, AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, KeyButMask, Property,
SelectionNotifyEvent, Timestamp, Window, WindowClass,
};
use crate::x11::connection::{X11Connection, X11Error, ignore_errors};
use crate::x11::xdnd;
use super::{
ExternalDndBackend, ExternalDndEventPayload, ExternalDndGuard, ExternalDragEvent, NoopDndGuard,
outbound_bytes, outbound_mimes,
};
const OUTBOUND_POLL: Duration = Duration::from_millis(8);
const FINISH_TIMEOUT: Duration = Duration::from_secs(5);
const INCR_THRESHOLD: usize = 128 * 1024;
#[derive(Default)]
pub struct X11ExternalDndBackend;
impl X11ExternalDndBackend {
pub fn new() -> Self {
Self
}
}
impl ExternalDndBackend for X11ExternalDndBackend {
fn attach(
&mut self,
parent: ParentHandle,
window_id: TeksiloWindowId,
poster: Arc<dyn AppEventPoster>,
) -> Box<dyn ExternalDndGuard> {
let Some(toplevel) = x11_window_id(&parent) else {
return Box::new(NoopDndGuard);
};
let shutdown = Arc::new(AtomicBool::new(false));
let scale = Arc::new(AtomicU64::new(1.0f64.to_bits()));
let (cmd_tx, cmd_rx) = channel();
let Ok(waker) = Waker::new() else {
eprintln!("teksilo-platform: X11 DnD disabled (could not create a wake pipe)");
return Box::new(NoopDndGuard);
};
let wake_read = waker.read_fd();
let thread_shutdown = shutdown.clone();
let thread_scale = scale.clone();
let handle = std::thread::Builder::new()
.name(format!("teksilo-xdnd-{}", window_id.raw()))
.spawn(move || {
if let Err(err) = run_thread(
toplevel,
window_id,
poster,
thread_shutdown,
thread_scale,
wake_read,
cmd_rx,
) {
eprintln!("teksilo-platform: X11 drag-and-drop thread stopped: {err}");
}
})
.ok();
Box::new(X11DndGuard {
shutdown,
scale,
commands: cmd_tx,
waker,
thread: Mutex::new(handle),
})
}
}
struct X11DndGuard {
shutdown: Arc<AtomicBool>,
scale: Arc<AtomicU64>,
commands: Sender<Command>,
waker: Waker,
thread: Mutex<Option<std::thread::JoinHandle<()>>>,
}
impl ExternalDndGuard for X11DndGuard {
fn begin_drag(&self, data: &OutboundDragData, _image: Option<&DragImageData>) -> bool {
if data.is_empty() {
return false;
}
if self.commands.send(Command::Begin(data.clone())).is_err() {
return false;
}
self.waker.wake();
true
}
fn cancel_drag(&self) {
let _ = self.commands.send(Command::Cancel);
self.waker.wake();
}
fn set_scale_factor(&self, scale: f64) {
if scale.is_finite() && scale > 0.0 {
self.scale.store(scale.to_bits(), Ordering::Relaxed);
}
}
}
impl Drop for X11DndGuard {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::SeqCst);
self.waker.wake();
if let Ok(mut slot) = self.thread.lock()
&& let Some(handle) = slot.take()
{
let _ = handle.join();
}
}
}
fn x11_window_id(parent: &ParentHandle) -> Option<Window> {
match parent.raw_window_handle() {
RawWindowHandle::Xlib(h) => Some(h.window as Window),
RawWindowHandle::Xcb(h) => Some(h.window.get()),
_ => None,
}
}
struct Waker {
read: RawFd,
write: RawFd,
}
impl Waker {
fn new() -> Result<Self, std::io::Error> {
let mut fds = [0 as libc::c_int; 2];
let rc = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) };
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(Self {
read: fds[0],
write: fds[1],
})
}
fn read_fd(&self) -> RawFd {
self.read
}
fn wake(&self) {
let byte = 1u8;
unsafe {
libc::write(self.write, std::ptr::from_ref(&byte).cast(), 1);
}
}
}
impl Drop for Waker {
fn drop(&mut self) {
unsafe {
libc::close(self.read);
libc::close(self.write);
}
}
}
fn wait_readable(x_fd: RawFd, wake_fd: RawFd, timeout: Option<Duration>) {
let mut fds = [
libc::pollfd {
fd: x_fd,
events: libc::POLLIN,
revents: 0,
},
libc::pollfd {
fd: wake_fd,
events: libc::POLLIN,
revents: 0,
},
];
let millis = timeout.map_or(-1, |d| d.as_millis().min(i32::MAX as u128) as libc::c_int);
unsafe {
libc::poll(fds.as_mut_ptr(), 2, millis);
}
if fds[1].revents & libc::POLLIN != 0 {
let mut buf = [0u8; 64];
while unsafe { libc::read(wake_fd, buf.as_mut_ptr().cast(), buf.len()) } > 0 {}
}
}
enum Command {
Begin(OutboundDragData),
Cancel,
}
fn run_thread(
toplevel: Window,
window_id: TeksiloWindowId,
poster: Arc<dyn AppEventPoster>,
shutdown: Arc<AtomicBool>,
scale: Arc<AtomicU64>,
wake_fd: RawFd,
commands: Receiver<Command>,
) -> Result<(), X11Error> {
let conn = X11Connection::open()?;
let proxy = create_proxy_window(&conn)?;
install_proxy(&conn, toplevel, proxy)?;
let x_fd = conn.conn().stream().as_raw_fd();
let mut state = DndThread {
conn,
poster,
window_id,
toplevel,
proxy,
scale,
inbound: None,
outbound: None,
incr_sends: Vec::new(),
};
let result = pump_until_shutdown(&mut state, &shutdown, &commands, x_fd, wake_fd);
state.teardown();
result
}
fn pump_until_shutdown(
state: &mut DndThread,
shutdown: &AtomicBool,
commands: &Receiver<Command>,
x_fd: RawFd,
wake_fd: RawFd,
) -> Result<(), X11Error> {
while !shutdown.load(Ordering::SeqCst) {
loop {
match state.conn.poll_event() {
Ok(Some(event)) => state.handle_event(event),
Ok(None) => break,
Err(err) => return Err(err),
}
}
while let Ok(command) = commands.try_recv() {
match command {
Command::Begin(data) => state.begin_outbound(data),
Command::Cancel => state.cancel_outbound(),
}
}
state.pump_outbound();
let timeout = state.outbound.is_some().then_some(OUTBOUND_POLL);
wait_readable(x_fd, wake_fd, timeout);
}
Ok(())
}
fn create_proxy_window(conn: &X11Connection) -> Result<Window, X11Error> {
let proxy = conn.conn().generate_id()?;
conn.conn()
.create_window(
0, proxy,
conn.root(),
0,
0,
1,
1,
0,
WindowClass::INPUT_ONLY,
0, &CreateWindowAux::new()
.override_redirect(1)
.event_mask(EventMask::PROPERTY_CHANGE),
)?
.check()?;
Ok(proxy)
}
fn install_proxy(conn: &X11Connection, toplevel: Window, proxy: Window) -> Result<(), X11Error> {
let atoms = conn.atoms();
conn.set_property32(
proxy,
atoms.xdnd_aware,
AtomEnum::ATOM.into(),
&[xdnd::XDND_VERSION],
)?;
conn.set_property32(proxy, atoms.xdnd_proxy, AtomEnum::WINDOW.into(), &[proxy])?;
conn.set_property32(
toplevel,
atoms.xdnd_proxy,
AtomEnum::WINDOW.into(),
&[proxy],
)?;
conn.flush()?;
Ok(())
}
struct Inbound {
source: Window,
version: u32,
types: Vec<Atom>,
chosen: Option<Atom>,
position: Point,
entered: bool,
incr: Option<xdnd::IncrAssembler>,
dropping: bool,
}
struct Outbound {
data: OutboundDragData,
types: Vec<(String, Atom)>,
time: Timestamp,
target: Option<Window>,
send_to: Option<Window>,
version: u32,
accepted: bool,
dropped_at: Option<Instant>,
resolved_for: Option<Window>,
}
struct IncrSend {
requestor: Window,
property: Atom,
type_: Atom,
remaining: Vec<u8>,
chunk: usize,
}
struct DndThread {
conn: X11Connection,
poster: Arc<dyn AppEventPoster>,
window_id: TeksiloWindowId,
toplevel: Window,
proxy: Window,
scale: Arc<AtomicU64>,
inbound: Option<Inbound>,
outbound: Option<Outbound>,
incr_sends: Vec<IncrSend>,
}
impl DndThread {
fn post(&self, event: ExternalDragEvent) {
self.poster.post_external(Box::new(ExternalDndEventPayload {
window_id_owner: self.window_id,
event,
}));
}
fn scale(&self) -> f64 {
f64::from_bits(self.scale.load(Ordering::Relaxed))
}
fn to_window_logical(&self, root_x: i16, root_y: i16) -> Option<Point> {
let reply = self
.conn
.conn()
.translate_coordinates(self.conn.root(), self.toplevel, root_x, root_y)
.ok()?
.reply()
.ok()?;
let scale = self.scale();
Some(Point::new(
(reply.dst_x as f64 / scale) as f32,
(reply.dst_y as f64 / scale) as f32,
))
}
fn handle_event(&mut self, event: Event) {
let atoms = self.conn.atoms().clone();
match event {
Event::ClientMessage(msg) => {
let data = msg.data.as_data32();
let data = [data[0], data[1], data[2], data[3], data[4]];
match msg.type_ {
t if t == atoms.xdnd_enter => self.on_enter(data),
t if t == atoms.xdnd_position => self.on_position(data),
t if t == atoms.xdnd_leave => self.on_leave(),
t if t == atoms.xdnd_drop => self.on_drop(data),
t if t == atoms.xdnd_status => self.on_status(data),
t if t == atoms.xdnd_finished => self.on_finished(data),
_ => {}
}
}
Event::SelectionNotify(notify) => self.on_selection_notify(notify.property),
Event::SelectionRequest(request) => self.on_selection_request(request),
Event::SelectionClear(_) => {
if self.outbound.is_some() {
self.finish_outbound(DropOutcome::Cancelled);
}
}
Event::PropertyNotify(notify) => {
if notify.window == self.proxy
&& notify.atom == atoms.teksilo_transfer
&& notify.state == Property::NEW_VALUE
{
self.on_incr_chunk();
} else if notify.state == Property::DELETE {
self.on_incr_property_deleted(notify.window, notify.atom);
}
}
_ => {}
}
}
fn on_enter(&mut self, data: [u32; 5]) {
let Some(enter) = xdnd::decode_enter(data) else {
return;
};
let mut types = enter.types;
if enter.more_types {
types = self
.conn
.get_property_full(
enter.source,
self.conn.atoms().xdnd_type_list,
AtomEnum::ATOM.into(),
)
.ok()
.flatten()
.map(|value| value.as_u32s())
.unwrap_or(types);
}
let chosen = xdnd::choose_type(&types, &self.conn.atoms().preferred_targets());
self.inbound = Some(Inbound {
source: enter.source,
version: enter.version,
types,
chosen,
position: Point::new(0.0, 0.0),
entered: false,
incr: None,
dropping: false,
});
}
fn on_position(&mut self, data: [u32; 5]) {
let position = xdnd::decode_position(data);
let Some(point) = self.to_window_logical(position.root_x, position.root_y) else {
return;
};
let (accept, first, types) = {
let Some(inbound) = self.inbound.as_mut() else {
return;
};
inbound.position = point;
let first = !inbound.entered;
inbound.entered = true;
let types = first.then(|| inbound.types.clone());
(inbound.chosen.is_some(), first, types)
};
let formats = types.map(|types| self.type_names(&types));
let action = self.conn.atoms().xdnd_action_copy;
self.send_to_source(
self.conn.atoms().xdnd_status,
xdnd::encode_status(self.toplevel, accept, action),
);
if first {
self.post(ExternalDragEvent::Entered {
data: ExternalDropData {
formats: formats.unwrap_or_default(),
..Default::default()
},
position: point,
});
} else {
self.post(ExternalDragEvent::Moved { position: point });
}
}
fn on_leave(&mut self) {
if self.inbound.take().is_some() {
self.post(ExternalDragEvent::Left);
}
}
fn on_drop(&mut self, data: [u32; 5]) {
let drop = xdnd::decode_drop(data);
let Some(inbound) = self.inbound.as_mut() else {
return;
};
let Some(chosen) = inbound.chosen else {
let (version, source) = (inbound.version, inbound.source);
self.finish_inbound(source, version, false);
self.inbound = None;
self.post(ExternalDragEvent::Left);
return;
};
inbound.dropping = true;
let source = inbound.source;
let atoms = self.conn.atoms();
let requested = self
.conn
.conn()
.convert_selection(
self.proxy,
atoms.xdnd_selection,
chosen,
atoms.teksilo_transfer,
drop.time,
)
.map(|cookie| cookie.check().is_ok())
.unwrap_or(false);
if !requested {
let (version, source) = self
.inbound
.as_ref()
.map_or((xdnd::XDND_VERSION, x11rb::NONE), |i| (i.version, i.source));
self.finish_inbound(source, version, false);
self.inbound = None;
self.post(ExternalDragEvent::Left);
return;
}
let _ = self.conn.flush();
let _ = source;
}
fn on_selection_notify(&mut self, property: Atom) {
if !self.inbound.as_ref().is_some_and(|i| i.dropping) {
return;
}
if property == x11rb::NONE {
let (version, source) = self
.inbound
.as_ref()
.map_or((xdnd::XDND_VERSION, x11rb::NONE), |i| (i.version, i.source));
self.finish_inbound(source, version, false);
self.inbound = None;
self.post(ExternalDragEvent::Left);
return;
}
let value = self
.conn
.get_property_and_delete(self.proxy, self.conn.atoms().teksilo_transfer)
.ok()
.flatten();
let Some(value) = value else {
self.complete_drop(Vec::new());
return;
};
if value.type_ == self.conn.atoms().incr {
let expected = value.as_u32().unwrap_or(0) as usize;
if let Some(inbound) = self.inbound.as_mut() {
inbound.incr = Some(xdnd::IncrAssembler::new(expected));
}
let _ = self.conn.flush();
return;
}
self.complete_drop(value.bytes);
}
fn on_incr_chunk(&mut self) {
if self.inbound.as_ref().is_none_or(|i| i.incr.is_none()) {
return;
}
let Some(value) = self
.conn
.get_property_and_delete(self.proxy, self.conn.atoms().teksilo_transfer)
.ok()
.flatten()
else {
return;
};
let _ = self.conn.flush();
let complete = self
.inbound
.as_mut()
.and_then(|i| i.incr.as_mut())
.map(|incr| incr.push(&value.bytes))
.unwrap_or(false);
if complete {
let bytes = self
.inbound
.as_mut()
.and_then(|i| i.incr.take())
.map(xdnd::IncrAssembler::finish)
.unwrap_or_default();
self.complete_drop(bytes);
}
}
fn complete_drop(&mut self, bytes: Vec<u8>) {
let Some(inbound) = self.inbound.take() else {
return;
};
let chosen = inbound.chosen.unwrap_or(x11rb::NONE);
let atoms = self.conn.atoms();
let text = String::from_utf8_lossy(&bytes).into_owned();
let mut data = if chosen == atoms.text_uri_list {
ExternalDropData::from_uri_list(&text)
} else {
ExternalDropData {
text: Some(text.clone()),
..Default::default()
}
};
if let Some(name) = self.atom_name(chosen) {
data.mime.insert(name, bytes);
}
data.formats = self.type_names(&inbound.types);
let accepted = !data.files.is_empty() || data.text.is_some() || !data.uris.is_empty();
self.post(ExternalDragEvent::Dropped {
data,
position: inbound.position,
});
self.finish_inbound(inbound.source, inbound.version, accepted);
}
fn finish_inbound(&mut self, source: Window, version: u32, accepted: bool) {
if source == x11rb::NONE {
return;
}
let action = self.conn.atoms().xdnd_action_copy;
let data = xdnd::encode_finished(self.toplevel, version, accepted, action);
let atom = self.conn.atoms().xdnd_finished;
let _ = self
.conn
.send_client_message(source, source, atom, data, EventMask::NO_EVENT);
let _ = self.conn.flush();
}
fn send_to_source(&self, type_: Atom, data: [u32; 5]) {
let Some(source) = self.inbound.as_ref().map(|i| i.source) else {
return;
};
let _ = self
.conn
.send_client_message(source, source, type_, data, EventMask::NO_EVENT);
let _ = self.conn.flush();
}
fn begin_outbound(&mut self, data: OutboundDragData) {
match self.try_begin_outbound(data) {
Ok(()) => {}
Err(_) => {
self.outbound = None;
self.post(ExternalDragEvent::DragEnded {
outcome: DropOutcome::Cancelled,
});
}
}
}
fn try_begin_outbound(&mut self, data: OutboundDragData) -> Result<(), X11Error> {
let mimes = outbound_mimes(&data);
let mut types = Vec::with_capacity(mimes.len());
for mime in mimes {
let atom = match self.conn.atoms().atom_for_mime(&mime) {
Some(atom) => atom,
None => {
self.conn
.conn()
.intern_atom(false, mime.as_bytes())?
.reply()?
.atom
}
};
types.push((mime, atom));
}
if types.is_empty() {
return Err(X11Error::Timeout(
"an outbound payload with at least one MIME type",
));
}
let time = self.conn.fetch_timestamp(self.proxy)?;
let atoms = self.conn.atoms().clone();
self.conn
.conn()
.set_selection_owner(self.proxy, atoms.xdnd_selection, time)?
.check()?;
if self
.conn
.conn()
.get_selection_owner(atoms.xdnd_selection)?
.reply()?
.owner
!= self.proxy
{
return Err(X11Error::Timeout("XdndSelection ownership"));
}
let type_atoms: Vec<u32> = types.iter().map(|(_, atom)| *atom).collect();
self.conn.set_property32(
self.proxy,
atoms.xdnd_type_list,
AtomEnum::ATOM.into(),
&type_atoms,
)?;
self.conn.set_property32(
self.proxy,
atoms.xdnd_action_list,
AtomEnum::ATOM.into(),
&[atoms.xdnd_action_copy],
)?;
self.conn.flush()?;
self.outbound = Some(Outbound {
data,
types,
time,
target: None,
send_to: None,
version: xdnd::XDND_VERSION,
accepted: false,
dropped_at: None,
resolved_for: None,
});
Ok(())
}
fn pump_outbound(&mut self) {
if self.outbound.is_none() {
return;
}
if let Some(started) = self.outbound.as_ref().and_then(|o| o.dropped_at) {
if started.elapsed() > FINISH_TIMEOUT {
self.finish_outbound(DropOutcome::OsCopy);
}
return;
}
let pointer = self
.conn
.conn()
.query_pointer(self.conn.root())
.ok()
.and_then(|cookie| cookie.reply().ok());
let Some(pointer) = pointer else {
self.finish_outbound(DropOutcome::Cancelled);
return;
};
let button_held = pointer.mask.contains(KeyButMask::BUTTON1);
let (root_x, root_y) = (pointer.root_x, pointer.root_y);
if !button_held {
self.release_outbound();
return;
}
let root_child = pointer.child;
let cached = self
.outbound
.as_ref()
.is_some_and(|o| o.resolved_for == Some(root_child));
if !cached {
let found = self.find_xdnd_target(root_x, root_y);
let previous = self
.outbound
.as_ref()
.and_then(|o| Some((o.send_to?, o.target?)));
if previous.map(|(_, target)| target) != found.map(|(target, ..)| target)
&& let Some((send_to, target)) = previous
{
self.send_outbound(
send_to,
target,
self.conn.atoms().xdnd_leave,
xdnd::encode_leave(self.proxy),
);
}
let entering = previous.map(|(_, target)| target) != found.map(|(target, ..)| target);
if let Some(outbound) = self.outbound.as_mut() {
outbound.resolved_for = Some(root_child);
match found {
Some((target, send_to, version)) => {
outbound.target = Some(target);
outbound.send_to = Some(send_to);
outbound.version = version;
}
None => {
outbound.target = None;
outbound.send_to = None;
}
}
if entering {
outbound.accepted = false;
}
}
if entering && let Some((target, send_to, version)) = found {
let type_atoms: Vec<Atom> = self
.outbound
.as_ref()
.map(|o| o.types.iter().map(|(_, atom)| *atom).collect())
.unwrap_or_default();
let data = xdnd::encode_enter(self.proxy, version, &type_atoms);
self.send_outbound(send_to, target, self.conn.atoms().xdnd_enter, data);
}
}
if let Some((send_to, target)) = self.outbound_address() {
let time = self.outbound.as_ref().map_or(0, |o| o.time);
let action = self.conn.atoms().xdnd_action_copy;
let data = xdnd::encode_position(self.proxy, root_x, root_y, time, action);
self.send_outbound(send_to, target, self.conn.atoms().xdnd_position, data);
}
}
fn outbound_address(&self) -> Option<(Window, Window)> {
let outbound = self.outbound.as_ref()?;
Some((outbound.send_to?, outbound.target?))
}
fn release_outbound(&mut self) {
let (accepted, time) = match self.outbound.as_ref() {
Some(o) => (o.accepted, o.time),
None => return,
};
match (self.outbound_address(), accepted) {
(Some((send_to, target)), true) => {
let data = xdnd::encode_drop(self.proxy, time);
self.send_outbound(send_to, target, self.conn.atoms().xdnd_drop, data);
if let Some(outbound) = self.outbound.as_mut() {
outbound.dropped_at = Some(Instant::now());
}
}
(Some((send_to, target)), false) => {
self.send_outbound(
send_to,
target,
self.conn.atoms().xdnd_leave,
xdnd::encode_leave(self.proxy),
);
self.finish_outbound(DropOutcome::Cancelled);
}
(None, _) => self.finish_outbound(DropOutcome::Cancelled),
}
}
fn cancel_outbound(&mut self) {
if let Some((send_to, target)) = self.outbound_address() {
self.send_outbound(
send_to,
target,
self.conn.atoms().xdnd_leave,
xdnd::encode_leave(self.proxy),
);
}
if self.outbound.is_some() {
self.finish_outbound(DropOutcome::Cancelled);
}
}
fn on_status(&mut self, data: [u32; 5]) {
let status = xdnd::decode_status(data);
if self
.outbound
.as_ref()
.is_some_and(|o| o.target.is_some_and(|t| t != status.target))
{
return;
}
if let Some(outbound) = self.outbound.as_mut() {
outbound.accepted = status.accepted;
}
}
fn on_finished(&mut self, data: [u32; 5]) {
let version = self
.outbound
.as_ref()
.map_or(xdnd::XDND_VERSION, |o| o.version);
let finished = xdnd::decode_finished(data, version);
if self.outbound.is_some() {
let outcome = if finished.accepted {
DropOutcome::OsCopy
} else {
DropOutcome::Cancelled
};
self.finish_outbound(outcome);
}
}
fn finish_outbound(&mut self, outcome: DropOutcome) {
if self.outbound.take().is_none() {
return;
}
let selection = self.conn.atoms().xdnd_selection;
ignore_errors(self.conn.conn().set_selection_owner(
x11rb::NONE,
selection,
x11rb::CURRENT_TIME,
));
let _ = self.conn.flush();
self.post(ExternalDragEvent::DragEnded { outcome });
}
fn send_outbound(&self, send_to: Window, target: Window, type_: Atom, data: [u32; 5]) {
let _ = self
.conn
.send_client_message(send_to, target, type_, data, EventMask::NO_EVENT);
let _ = self.conn.flush();
}
fn find_xdnd_target(&self, root_x: i16, root_y: i16) -> Option<(Window, Window, u32)> {
let root = self.conn.root();
let mut current = root;
for _ in 0..32 {
let reply = self
.conn
.conn()
.translate_coordinates(root, current, root_x, root_y)
.ok()?
.reply()
.ok()?;
if reply.child == x11rb::NONE {
break;
}
current = reply.child;
}
let atoms = self.conn.atoms();
let mut candidate = current;
for _ in 0..32 {
if candidate == x11rb::NONE || candidate == root {
return None;
}
if let Some(version) = self
.conn
.get_property_full(candidate, atoms.xdnd_aware, AtomEnum::ATOM.into())
.ok()
.flatten()
.and_then(|value| value.as_u32())
.and_then(xdnd::negotiate_version)
{
let proxy = self
.conn
.get_property_full(candidate, atoms.xdnd_proxy, AtomEnum::WINDOW.into())
.ok()
.flatten()
.and_then(|value| value.as_u32());
let proxy_self = proxy.and_then(|p| {
self.conn
.get_property_full(p, atoms.xdnd_proxy, AtomEnum::WINDOW.into())
.ok()
.flatten()
.and_then(|value| value.as_u32())
});
let send_to = xdnd::resolve_proxy(candidate, proxy, proxy_self);
return Some((candidate, send_to, version));
}
candidate = self
.conn
.conn()
.query_tree(candidate)
.ok()?
.reply()
.ok()
.map(|reply| reply.parent)?;
}
None
}
fn on_selection_request(&mut self, request: x11rb::protocol::xproto::SelectionRequestEvent) {
let atoms = self.conn.atoms().clone();
if request.selection != atoms.xdnd_selection {
return;
}
let property = if request.property == x11rb::NONE {
request.target
} else {
request.property
};
let served = self.serve_selection(&request, property, &atoms);
let notify = SelectionNotifyEvent {
response_type: x11rb::protocol::xproto::SELECTION_NOTIFY_EVENT,
sequence: 0,
time: request.time,
requestor: request.requestor,
selection: request.selection,
target: request.target,
property: if served { property } else { x11rb::NONE },
};
ignore_errors(self.conn.conn().send_event(
false,
request.requestor,
EventMask::NO_EVENT,
notify,
));
let _ = self.conn.flush();
}
fn serve_selection(
&mut self,
request: &x11rb::protocol::xproto::SelectionRequestEvent,
property: Atom,
atoms: &crate::x11::Atoms,
) -> bool {
let Some(outbound) = self.outbound.as_ref() else {
return false;
};
if request.target == atoms.targets {
let mut list: Vec<u32> = outbound.types.iter().map(|(_, atom)| *atom).collect();
list.push(atoms.targets);
return self
.conn
.set_property32(request.requestor, property, AtomEnum::ATOM.into(), &list)
.is_ok();
}
if request.target == atoms.timestamp {
return self
.conn
.set_property32(
request.requestor,
property,
AtomEnum::INTEGER.into(),
&[outbound.time],
)
.is_ok();
}
let Some((mime, _)) = outbound
.types
.iter()
.find(|(_, atom)| *atom == request.target)
else {
return false;
};
let bytes = outbound_bytes(&outbound.data, mime);
if bytes.len() <= INCR_THRESHOLD {
return self
.conn
.set_property8(request.requestor, property, request.target, &bytes)
.is_ok();
}
let selected = self
.conn
.conn()
.change_window_attributes(
request.requestor,
&x11rb::protocol::xproto::ChangeWindowAttributesAux::new()
.event_mask(EventMask::PROPERTY_CHANGE),
)
.map(|cookie| cookie.check().is_ok())
.unwrap_or(false);
if !selected {
return false;
}
let total = [bytes.len() as u32];
if self
.conn
.set_property32(request.requestor, property, atoms.incr, &total)
.is_err()
{
return false;
}
let chunk = self.max_chunk_bytes();
self.incr_sends.push(IncrSend {
requestor: request.requestor,
property,
type_: request.target,
remaining: bytes,
chunk,
});
true
}
fn on_incr_property_deleted(&mut self, window: Window, property: Atom) {
let Some(index) = self
.incr_sends
.iter()
.position(|send| send.requestor == window && send.property == property)
else {
return;
};
let (requestor, prop, type_, chunk, done) = {
let send = &mut self.incr_sends[index];
let take = send.chunk.min(send.remaining.len());
let chunk: Vec<u8> = send.remaining.drain(..take).collect();
let done = chunk.is_empty();
(send.requestor, send.property, send.type_, chunk, done)
};
let _ = self.conn.set_property8(requestor, prop, type_, &chunk);
let _ = self.conn.flush();
if done {
self.incr_sends.remove(index);
}
}
fn max_chunk_bytes(&self) -> usize {
let units = self.conn.conn().setup().maximum_request_length as usize;
(units.saturating_mul(4) / 2).clamp(4096, INCR_THRESHOLD)
}
fn atom_name(&self, atom: Atom) -> Option<String> {
if atom == x11rb::NONE {
return None;
}
let reply = self.conn.conn().get_atom_name(atom).ok()?.reply().ok()?;
Some(String::from_utf8_lossy(&reply.name).into_owned())
}
fn type_names(&self, types: &[Atom]) -> Vec<String> {
types
.iter()
.filter_map(|&atom| self.atom_name(atom))
.collect()
}
fn teardown(&mut self) {
if self.outbound.is_some() {
self.finish_outbound(DropOutcome::Cancelled);
}
let atoms = self.conn.atoms().clone();
ignore_errors(
self.conn
.conn()
.delete_property(self.toplevel, atoms.xdnd_proxy),
);
ignore_errors(self.conn.conn().destroy_window(self.proxy));
}
}
#[cfg(test)]
mod tests;