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    // Populated by XdndEnter event handler
44    pub version: Option<c_long>,
45    pub type_list: Option<Vec<xproto::Atom>>,
46    // Populated by XdndPosition event handler
47    pub source_window: Option<xproto::Window>,
48    // Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
49    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                // The format is specified as protocol://host/path
152                // However, it's typically simply protocol:///path
153                let path_str = if uri.starts_with("file://") {
154                    let path_str = uri.replace("file://", "");
155                    if !path_str.starts_with('/') {
156                        // A hostname is specified
157                        // Supporting this case is beyond the scope of my mental health
158                        return Err(DndDataParseError::HostnameSpecified(path_str));
159                    }
160                    path_str
161                } else {
162                    // Only the file protocol is supported
163                    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}