winit/platform_impl/linux/x11/
dnd.rs1use std::{
2 io,
3 os::raw::*,
4 path::{Path, PathBuf},
5 str::Utf8Error,
6 sync::Arc,
7};
8
9use percent_encoding::percent_decode;
10
11use super::{ffi, util, XConnection, XError};
12
13#[derive(Debug)]
14pub struct DndAtoms {
15 pub aware: ffi::Atom,
16 pub enter: ffi::Atom,
17 pub leave: ffi::Atom,
18 pub drop: ffi::Atom,
19 pub position: ffi::Atom,
20 pub status: ffi::Atom,
21 pub action_private: ffi::Atom,
22 pub selection: ffi::Atom,
23 pub finished: ffi::Atom,
24 pub type_list: ffi::Atom,
25 pub uri_list: ffi::Atom,
26 pub none: ffi::Atom,
27}
28
29impl DndAtoms {
30 pub fn new(xconn: &Arc<XConnection>) -> Result<Self, XError> {
31 let names = [
32 b"XdndAware\0".as_ptr() as *mut c_char,
33 b"XdndEnter\0".as_ptr() as *mut c_char,
34 b"XdndLeave\0".as_ptr() as *mut c_char,
35 b"XdndDrop\0".as_ptr() as *mut c_char,
36 b"XdndPosition\0".as_ptr() as *mut c_char,
37 b"XdndStatus\0".as_ptr() as *mut c_char,
38 b"XdndActionPrivate\0".as_ptr() as *mut c_char,
39 b"XdndSelection\0".as_ptr() as *mut c_char,
40 b"XdndFinished\0".as_ptr() as *mut c_char,
41 b"XdndTypeList\0".as_ptr() as *mut c_char,
42 b"text/uri-list\0".as_ptr() as *mut c_char,
43 b"None\0".as_ptr() as *mut c_char,
44 ];
45 let atoms = unsafe { xconn.get_atoms(&names) }?;
46 Ok(DndAtoms {
47 aware: atoms[0],
48 enter: atoms[1],
49 leave: atoms[2],
50 drop: atoms[3],
51 position: atoms[4],
52 status: atoms[5],
53 action_private: atoms[6],
54 selection: atoms[7],
55 finished: atoms[8],
56 type_list: atoms[9],
57 uri_list: atoms[10],
58 none: atoms[11],
59 })
60 }
61}
62
63#[derive(Debug, Clone, Copy)]
64pub enum DndState {
65 Accepted,
66 Rejected,
67}
68
69#[derive(Debug)]
70pub enum DndDataParseError {
71 EmptyData,
72 InvalidUtf8(Utf8Error),
73 HostnameSpecified(String),
74 UnexpectedProtocol(String),
75 UnresolvablePath(io::Error),
76}
77
78impl From<Utf8Error> for DndDataParseError {
79 fn from(e: Utf8Error) -> Self {
80 DndDataParseError::InvalidUtf8(e)
81 }
82}
83
84impl From<io::Error> for DndDataParseError {
85 fn from(e: io::Error) -> Self {
86 DndDataParseError::UnresolvablePath(e)
87 }
88}
89
90pub struct Dnd {
91 xconn: Arc<XConnection>,
92 pub atoms: DndAtoms,
93 pub version: Option<c_long>,
95 pub type_list: Option<Vec<c_ulong>>,
96 pub source_window: Option<c_ulong>,
98 pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
100}
101
102impl Dnd {
103 pub fn new(xconn: Arc<XConnection>) -> Result<Self, XError> {
104 let atoms = DndAtoms::new(&xconn)?;
105 Ok(Dnd {
106 xconn,
107 atoms,
108 version: None,
109 type_list: None,
110 source_window: None,
111 result: None,
112 })
113 }
114
115 pub fn reset(&mut self) {
116 self.version = None;
117 self.type_list = None;
118 self.source_window = None;
119 self.result = None;
120 }
121
122 pub unsafe fn send_status(
123 &self,
124 this_window: c_ulong,
125 target_window: c_ulong,
126 state: DndState,
127 ) -> Result<(), XError> {
128 let (accepted, action) = match state {
129 DndState::Accepted => (1, self.atoms.action_private as c_long),
130 DndState::Rejected => (0, self.atoms.none as c_long),
131 };
132 self.xconn
133 .send_client_msg(
134 target_window,
135 target_window,
136 self.atoms.status,
137 None,
138 [this_window as c_long, accepted, 0, 0, action],
139 )
140 .flush()
141 }
142
143 pub unsafe fn send_finished(
144 &self,
145 this_window: c_ulong,
146 target_window: c_ulong,
147 state: DndState,
148 ) -> Result<(), XError> {
149 let (accepted, action) = match state {
150 DndState::Accepted => (1, self.atoms.action_private as c_long),
151 DndState::Rejected => (0, self.atoms.none as c_long),
152 };
153 self.xconn
154 .send_client_msg(
155 target_window,
156 target_window,
157 self.atoms.finished,
158 None,
159 [this_window as c_long, accepted, action, 0, 0],
160 )
161 .flush()
162 }
163
164 pub unsafe fn get_type_list(
165 &self,
166 source_window: c_ulong,
167 ) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
168 self.xconn
169 .get_property(source_window, self.atoms.type_list, ffi::XA_ATOM)
170 }
171
172 pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) {
173 (self.xconn.xlib.XConvertSelection)(
174 self.xconn.display,
175 self.atoms.selection,
176 self.atoms.uri_list,
177 self.atoms.selection,
178 window,
179 time,
180 );
181 }
182
183 pub unsafe fn read_data(
184 &self,
185 window: c_ulong,
186 ) -> Result<Vec<c_uchar>, util::GetPropertyError> {
187 self.xconn
188 .get_property(window, self.atoms.selection, self.atoms.uri_list)
189 }
190
191 pub fn parse_data(&self, data: &mut Vec<c_uchar>) -> Result<Vec<PathBuf>, DndDataParseError> {
192 if !data.is_empty() {
193 let mut path_list = Vec::new();
194 let decoded = percent_decode(data).decode_utf8()?.into_owned();
195 for uri in decoded.split("\r\n").filter(|u| !u.is_empty()) {
196 let path_str = if uri.starts_with("file://") {
199 let path_str = uri.replace("file://", "");
200 if !path_str.starts_with('/') {
201 return Err(DndDataParseError::HostnameSpecified(path_str));
204 }
205 path_str
206 } else {
207 return Err(DndDataParseError::UnexpectedProtocol(uri.to_owned()));
209 };
210
211 let path = Path::new(&path_str).canonicalize()?;
212 path_list.push(path);
213 }
214 Ok(path_list)
215 } else {
216 Err(DndDataParseError::EmptyData)
217 }
218 }
219}