winit-sonoma-fix/src/platform/linux/x11/dnd.rs

241 lines
7.1 KiB
Rust
Raw Normal View History

use std::io;
use std::sync::Arc;
use std::path::{Path, PathBuf};
use std::str::Utf8Error;
use libc::{c_char, c_int, c_long, c_uchar, c_ulong};
use percent_encoding::percent_decode;
use super::{ffi, util, XConnection, XError};
const DND_ATOMS_LEN: usize = 12;
#[derive(Debug)]
pub struct DndAtoms {
pub aware: ffi::Atom,
pub enter: ffi::Atom,
pub leave: ffi::Atom,
pub drop: ffi::Atom,
pub position: ffi::Atom,
pub status: ffi::Atom,
pub action_private: ffi::Atom,
pub selection: ffi::Atom,
pub finished: ffi::Atom,
pub type_list: ffi::Atom,
pub uri_list: ffi::Atom,
pub none: ffi::Atom,
}
impl DndAtoms {
pub fn new(xconn: &Arc<XConnection>) -> Result<Self, XError> {
let mut atoms = Vec::with_capacity(DND_ATOMS_LEN);
let mut names = [
b"XdndAware\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndEnter\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndLeave\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndDrop\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndPosition\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndStatus\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndActionPrivate\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndSelection\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndFinished\0".to_owned().as_mut_ptr() as *mut c_char,
b"XdndTypeList\0".to_owned().as_mut_ptr() as *mut c_char,
b"text/uri-list\0".to_owned().as_mut_ptr() as *mut c_char,
b"None\0".to_owned().as_mut_ptr() as *mut c_char,
];
unsafe {
(xconn.xlib.XInternAtoms)(
xconn.display,
names.as_mut_ptr(),
DND_ATOMS_LEN as c_int,
ffi::False,
atoms.as_mut_ptr(),
);
}
xconn.check_errors()?;
unsafe {
atoms.set_len(DND_ATOMS_LEN);
}
Ok(DndAtoms {
aware: atoms[0],
enter: atoms[1],
leave: atoms[2],
drop: atoms[3],
position: atoms[4],
status: atoms[5],
action_private: atoms[6],
selection: atoms[7],
finished: atoms[8],
type_list: atoms[9],
uri_list: atoms[10],
none: atoms[11],
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum DndState {
Accepted,
Rejected,
}
#[derive(Debug)]
pub enum DndDataParseError {
EmptyData,
InvalidUtf8(Utf8Error),
HostnameSpecified(String),
UnexpectedProtocol(String),
UnresolvablePath(io::Error),
}
impl From<Utf8Error> for DndDataParseError {
fn from(e: Utf8Error) -> Self {
DndDataParseError::InvalidUtf8(e)
}
}
impl From<io::Error> for DndDataParseError {
fn from(e: io::Error) -> Self {
DndDataParseError::UnresolvablePath(e)
}
}
pub struct Dnd {
xconn: Arc<XConnection>,
pub atoms: DndAtoms,
// Populated by XdndEnter event handler
pub version: Option<c_long>,
pub type_list: Option<Vec<c_ulong>>,
// Populated by XdndPosition event handler
pub source_window: Option<c_ulong>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
}
impl Dnd {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, XError> {
let atoms = DndAtoms::new(&xconn)?;
Ok(Dnd {
xconn,
atoms,
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: c_ulong,
target_window: c_ulong,
state: DndState,
) -> Result<(), XError> {
let (accepted, action) = match state {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
};
util::send_client_msg(
&self.xconn,
target_window,
target_window,
self.atoms.status,
None,
(this_window as c_long, accepted, 0, 0, action),
)
}
pub unsafe fn send_finished(
&self,
this_window: c_ulong,
target_window: c_ulong,
state: DndState,
) -> Result<(), XError> {
let (accepted, action) = match state {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
};
util::send_client_msg(
&self.xconn,
target_window,
target_window,
self.atoms.finished,
None,
(this_window as c_long, accepted, action, 0, 0),
)
}
pub unsafe fn get_type_list(
&self,
source_window: c_ulong,
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
util::get_property(
&self.xconn,
source_window,
self.atoms.type_list,
ffi::XA_ATOM,
)
}
pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) {
(self.xconn.xlib.XConvertSelection)(
self.xconn.display,
self.atoms.selection,
self.atoms.uri_list,
self.atoms.selection,
window,
time,
);
}
pub unsafe fn read_data(
&self,
window: c_ulong,
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
util::get_property(
&self.xconn,
window,
self.atoms.selection,
self.atoms.uri_list,
)
}
pub fn parse_data(&self, data: &mut Vec<c_uchar>) -> Result<Vec<PathBuf>, 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)
}
}
}