winit/platform_impl/linux/x11/
dnd.rs
1use std::io;
2use std::os::raw::*;
3use std::path::{Path, PathBuf};
4use std::str::Utf8Error;
5use std::sync::Arc;
6
7use percent_encoding::percent_decode;
8use x11rb::protocol::xproto::{self, ConnectionExt};
9
10use super::atoms::AtomName::None as DndNone;
11use super::atoms::*;
12use super::{util, CookieResultExt, X11Error, XConnection};
13
14#[derive(Debug, Clone, Copy)]
15pub enum DndState {
16 Accepted,
17 Rejected,
18}
19
20#[derive(Debug)]
21pub enum DndDataParseError {
22 EmptyData,
23 InvalidUtf8(#[allow(dead_code)] Utf8Error),
24 HostnameSpecified(#[allow(dead_code)] String),
25 UnexpectedProtocol(#[allow(dead_code)] String),
26 UnresolvablePath(#[allow(dead_code)] io::Error),
27}
28
29impl From<Utf8Error> for DndDataParseError {
30 fn from(e: Utf8Error) -> Self {
31 DndDataParseError::InvalidUtf8(e)
32 }
33}
34
35impl From<io::Error> for DndDataParseError {
36 fn from(e: io::Error) -> Self {
37 DndDataParseError::UnresolvablePath(e)
38 }
39}
40
41pub struct Dnd {
42 xconn: Arc<XConnection>,
43 pub version: Option<c_long>,
45 pub type_list: Option<Vec<xproto::Atom>>,
46 pub source_window: Option<xproto::Window>,
48 pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
50}
51
52impl Dnd {
53 pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
54 Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None })
55 }
56
57 pub fn reset(&mut self) {
58 self.version = None;
59 self.type_list = None;
60 self.source_window = None;
61 self.result = None;
62 }
63
64 pub unsafe fn send_status(
65 &self,
66 this_window: xproto::Window,
67 target_window: xproto::Window,
68 state: DndState,
69 ) -> Result<(), X11Error> {
70 let atoms = self.xconn.atoms();
71 let (accepted, action) = match state {
72 DndState::Accepted => (1, atoms[XdndActionPrivate]),
73 DndState::Rejected => (0, atoms[DndNone]),
74 };
75 self.xconn
76 .send_client_msg(target_window, target_window, atoms[XdndStatus] as _, None, [
77 this_window,
78 accepted,
79 0,
80 0,
81 action as _,
82 ])?
83 .ignore_error();
84
85 Ok(())
86 }
87
88 pub unsafe fn send_finished(
89 &self,
90 this_window: xproto::Window,
91 target_window: xproto::Window,
92 state: DndState,
93 ) -> Result<(), X11Error> {
94 let atoms = self.xconn.atoms();
95 let (accepted, action) = match state {
96 DndState::Accepted => (1, atoms[XdndActionPrivate]),
97 DndState::Rejected => (0, atoms[DndNone]),
98 };
99 self.xconn
100 .send_client_msg(target_window, target_window, atoms[XdndFinished] as _, None, [
101 this_window,
102 accepted,
103 action as _,
104 0,
105 0,
106 ])?
107 .ignore_error();
108
109 Ok(())
110 }
111
112 pub unsafe fn get_type_list(
113 &self,
114 source_window: xproto::Window,
115 ) -> Result<Vec<xproto::Atom>, util::GetPropertyError> {
116 let atoms = self.xconn.atoms();
117 self.xconn.get_property(
118 source_window,
119 atoms[XdndTypeList],
120 xproto::Atom::from(xproto::AtomEnum::ATOM),
121 )
122 }
123
124 pub unsafe fn convert_selection(&self, window: xproto::Window, time: xproto::Timestamp) {
125 let atoms = self.xconn.atoms();
126 self.xconn
127 .xcb_connection()
128 .convert_selection(
129 window,
130 atoms[XdndSelection],
131 atoms[TextUriList],
132 atoms[XdndSelection],
133 time,
134 )
135 .expect_then_ignore_error("Failed to send XdndSelection event")
136 }
137
138 pub unsafe fn read_data(
139 &self,
140 window: xproto::Window,
141 ) -> Result<Vec<c_uchar>, util::GetPropertyError> {
142 let atoms = self.xconn.atoms();
143 self.xconn.get_property(window, atoms[XdndSelection], atoms[TextUriList])
144 }
145
146 pub fn parse_data(&self, data: &mut [c_uchar]) -> Result<Vec<PathBuf>, DndDataParseError> {
147 if !data.is_empty() {
148 let mut path_list = Vec::new();
149 let decoded = percent_decode(data).decode_utf8()?.into_owned();
150 for uri in decoded.split("\r\n").filter(|u| !u.is_empty()) {
151 let path_str = if uri.starts_with("file://") {
154 let path_str = uri.replace("file://", "");
155 if !path_str.starts_with('/') {
156 return Err(DndDataParseError::HostnameSpecified(path_str));
159 }
160 path_str
161 } else {
162 return Err(DndDataParseError::UnexpectedProtocol(uri.to_owned()));
164 };
165
166 let path = Path::new(&path_str).canonicalize()?;
167 path_list.push(path);
168 }
169 Ok(path_list)
170 } else {
171 Err(DndDataParseError::EmptyData)
172 }
173 }
174}