use std::io; use std::os::raw::*; use std::path::{Path, PathBuf}; use std::str::Utf8Error; use std::sync::Arc; use percent_encoding::percent_decode; use x11rb::protocol::xproto::{self, ConnectionExt}; use super::atoms::AtomName::None as DndNone; use super::atoms::*; use super::{util, CookieResultExt, X11Error, XConnection}; #[derive(Debug, Clone, Copy)] pub enum DndState { Accepted, Rejected, } #[derive(Debug)] pub enum DndDataParseError { EmptyData, InvalidUtf8(#[allow(dead_code)] Utf8Error), HostnameSpecified(#[allow(dead_code)] String), UnexpectedProtocol(#[allow(dead_code)] String), UnresolvablePath(#[allow(dead_code)] io::Error), } impl From for DndDataParseError { fn from(e: Utf8Error) -> Self { DndDataParseError::InvalidUtf8(e) } } impl From for DndDataParseError { fn from(e: io::Error) -> Self { DndDataParseError::UnresolvablePath(e) } } pub struct Dnd { xconn: Arc, // Populated by XdndEnter event handler pub version: Option, pub type_list: Option>, // Populated by XdndPosition event handler pub source_window: Option, // Populated by SelectionNotify event handler (triggered by XdndPosition event handler) pub result: Option, DndDataParseError>>, } impl Dnd { pub fn new(xconn: Arc) -> Result { Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None }) } pub fn reset(&mut self) { self.version = None; self.type_list = None; self.source_window = None; self.result = None; } pub unsafe fn send_status( &self, this_window: xproto::Window, target_window: xproto::Window, state: DndState, ) -> Result<(), X11Error> { let atoms = self.xconn.atoms(); let (accepted, action) = match state { DndState::Accepted => (1, atoms[XdndActionPrivate]), DndState::Rejected => (0, atoms[DndNone]), }; self.xconn .send_client_msg(target_window, target_window, atoms[XdndStatus] as _, None, [ this_window, accepted, 0, 0, action as _, ])? .ignore_error(); Ok(()) } pub unsafe fn send_finished( &self, this_window: xproto::Window, target_window: xproto::Window, state: DndState, ) -> Result<(), X11Error> { let atoms = self.xconn.atoms(); let (accepted, action) = match state { DndState::Accepted => (1, atoms[XdndActionPrivate]), DndState::Rejected => (0, atoms[DndNone]), }; self.xconn .send_client_msg(target_window, target_window, atoms[XdndFinished] as _, None, [ this_window, accepted, action as _, 0, 0, ])? .ignore_error(); Ok(()) } pub unsafe fn get_type_list( &self, source_window: xproto::Window, ) -> Result, util::GetPropertyError> { let atoms = self.xconn.atoms(); self.xconn.get_property( source_window, atoms[XdndTypeList], xproto::Atom::from(xproto::AtomEnum::ATOM), ) } pub unsafe fn convert_selection(&self, window: xproto::Window, time: xproto::Timestamp) { let atoms = self.xconn.atoms(); self.xconn .xcb_connection() .convert_selection( window, atoms[XdndSelection], atoms[TextUriList], atoms[XdndSelection], time, ) .expect_then_ignore_error("Failed to send XdndSelection event") } pub unsafe fn read_data( &self, window: xproto::Window, ) -> Result, util::GetPropertyError> { let atoms = self.xconn.atoms(); self.xconn.get_property(window, atoms[XdndSelection], atoms[TextUriList]) } pub fn parse_data(&self, data: &mut [c_uchar]) -> Result, DndDataParseError> { if !data.is_empty() { let mut path_list = Vec::new(); let decoded = percent_decode(data).decode_utf8()?.into_owned(); for uri in decoded.split("\r\n").filter(|u| !u.is_empty()) { // The format is specified as protocol://host/path // However, it's typically simply protocol:///path let path_str = if uri.starts_with("file://") { let path_str = uri.replace("file://", ""); if !path_str.starts_with('/') { // A hostname is specified // Supporting this case is beyond the scope of my mental health return Err(DndDataParseError::HostnameSpecified(path_str)); } path_str } else { // Only the file protocol is supported return Err(DndDataParseError::UnexpectedProtocol(uri.to_owned())); }; let path = Path::new(&path_str).canonicalize()?; path_list.push(path); } Ok(path_list) } else { Err(DndDataParseError::EmptyData) } } }