nativeshell/shell/platform/linux/
drag_data.rs

1use std::{collections::HashMap, mem::take};
2
3use gdk::Atom;
4use gtk::SelectionData;
5use log::warn;
6use percent_encoding::percent_decode_str;
7use url::Url;
8
9use crate::{
10    codec::{MessageCodec, StandardMethodCodec, Value},
11    shell::{api_constants::drag_data, ContextOptions},
12};
13
14pub trait DragDataSetter {
15    fn set(&self, selection_data: &SelectionData);
16    fn data_formats(&self) -> Vec<Atom>;
17}
18
19pub trait DragDataAdapter {
20    fn retrieve_drag_data(&self, data: &SelectionData, data_out: &mut HashMap<String, Value>);
21    fn data_formats(&self) -> Vec<Atom>;
22
23    fn prepare_drag_data(
24        &self,
25        data_in: &mut HashMap<String, Value>,
26    ) -> Vec<Box<dyn DragDataSetter>>;
27}
28
29pub(super) struct UriListDataAdapter {}
30
31impl UriListDataAdapter {
32    pub fn new() -> Self {
33        Self {}
34    }
35}
36
37const FRAGMENT: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
38    .add(b' ')
39    .add(b'"')
40    .add(b'<')
41    .add(b'>')
42    .add(b'`');
43
44impl DragDataAdapter for UriListDataAdapter {
45    fn retrieve_drag_data(&self, data: &SelectionData, data_out: &mut HashMap<String, Value>) {
46        let mut uris = Vec::<String>::new();
47        let data_uris = data.uris();
48        for uri in data_uris {
49            let uri = uri.trim().to_string();
50            if !uris.contains(&uri) {
51                uris.push(uri);
52            }
53        }
54        if let Some(string) = data.text() {
55            let parts = string.split('\n');
56            for part in parts {
57                let part = part.trim().to_string();
58                if !part.is_empty() && !uris.contains(&part) {
59                    uris.push(part);
60                }
61            }
62        }
63
64        let mut res_uris = Vec::<String>::new();
65        let mut res_paths = Vec::<String>::new();
66
67        for uri in uris {
68            if let Ok(parsed) = Url::parse(&uri) {
69                if parsed.scheme() == "file"
70                    && parsed.query().is_none()
71                    && parsed.fragment().is_none()
72                {
73                    res_paths.push(percent_decode_str(parsed.path()).decode_utf8_lossy().into());
74                    continue;
75                }
76            }
77
78            res_uris.push(uri);
79        }
80
81        let res_uris: Vec<Value> = res_uris.iter().map(|s| Value::String(s.into())).collect();
82        let res_paths: Vec<Value> = res_paths.iter().map(|s| Value::String(s.into())).collect();
83
84        if !res_uris.is_empty() {
85            data_out.insert(drag_data::key::URLS.into(), Value::List(res_uris));
86        }
87        if !res_paths.is_empty() {
88            data_out.insert(drag_data::key::FILES.into(), Value::List(res_paths));
89        }
90    }
91
92    fn data_formats(&self) -> Vec<Atom> {
93        vec![
94            Atom::intern("text/uri-list"),
95            Atom::intern("UTF8_STRING"),
96            Atom::intern("COMPOUND_TEXT"),
97            Atom::intern("TEXT"),
98            Atom::intern("STRING"),
99            Atom::intern("text/plain;charset=utf-8"),
100            Atom::intern("text/plain"),
101        ]
102    }
103
104    fn prepare_drag_data(
105        &self,
106        data_in: &mut HashMap<String, Value>,
107    ) -> Vec<Box<dyn DragDataSetter>> {
108        let mut uris = Vec::<String>::new();
109
110        let urls = data_in.remove(drag_data::key::URLS);
111        if let Some(mut urls) = extract_string_list(urls) {
112            uris.append(&mut urls);
113        }
114
115        let files = data_in.remove(drag_data::key::FILES);
116        if let Some(files) = extract_string_list(files) {
117            for file in files {
118                let uri = format!(
119                    "file://{}",
120                    percent_encoding::utf8_percent_encode(&file, FRAGMENT)
121                );
122                if !uris.contains(&uri) {
123                    uris.push(uri);
124                }
125            }
126        }
127
128        vec![
129            Box::new(UriDragData {
130                uris: uris.clone(),
131                set_as_uris: true,
132                formats: vec![Atom::intern("text/uri-list")],
133            }),
134            Box::new(UriDragData {
135                uris,
136                set_as_uris: false,
137                formats: vec![
138                    Atom::intern("UTF8_STRING"),
139                    Atom::intern("COMPOUND_TEXT"),
140                    Atom::intern("TEXT"),
141                    Atom::intern("STRING"),
142                    Atom::intern("text/plain;charset=utf-8"),
143                    Atom::intern("text/plain"),
144                ],
145            }),
146        ]
147    }
148}
149
150fn extract_string_list(value: Option<Value>) -> Option<Vec<String>> {
151    match value {
152        Some(value) => {
153            if let Value::List(list) = value {
154                let mut res = Vec::new();
155                for value in list {
156                    if let Value::String(value) = value {
157                        res.push(value)
158                    } else {
159                        panic!("Invalid value found in list: ${:?}", value);
160                    }
161                }
162                return Some(res);
163            }
164            panic!("Invalid value: {:?}, expected list of strings", value)
165        }
166        None => None,
167    }
168}
169
170struct UriDragData {
171    uris: Vec<String>,
172    set_as_uris: bool,
173    formats: Vec<Atom>,
174}
175
176impl DragDataSetter for UriDragData {
177    fn set(&self, selection_data: &SelectionData) {
178        if self.set_as_uris {
179            let uris: Vec<&str> = self.uris.iter().map(|s| s.as_str()).collect();
180            selection_data.set_uris(&uris);
181        } else {
182            let mut str = String::new();
183            for uri in &self.uris {
184                str.push_str(uri);
185                str.push('\n');
186            }
187            selection_data.set_text(&str);
188        }
189    }
190
191    fn data_formats(&self) -> Vec<Atom> {
192        self.formats.clone()
193    }
194}
195
196//
197//
198//
199
200pub struct FallThroughDragDataAdapter {
201    format: Atom,
202}
203
204impl FallThroughDragDataAdapter {
205    pub fn new(context_option: &ContextOptions) -> Self {
206        Self {
207            format: Atom::intern(&format!(
208                "FLUTTER_INTERNAL/{}",
209                context_option.app_namespace
210            )),
211        }
212    }
213}
214
215impl DragDataAdapter for FallThroughDragDataAdapter {
216    fn retrieve_drag_data(&self, data: &SelectionData, data_out: &mut HashMap<String, Value>) {
217        let codec: &'static dyn MessageCodec<Value> = &StandardMethodCodec;
218        let data = data.data();
219        let value = codec.decode_message(&data).unwrap();
220        if let Value::Map(value) = value {
221            for entry in value {
222                if let Value::String(key) = entry.0 {
223                    data_out.insert(key, entry.1);
224                } else {
225                    warn!("Unexpected key type {:?}", entry.0);
226                }
227            }
228        } else {
229            warn!("Unexpected value in clipboard {:?}", value);
230        }
231    }
232
233    fn data_formats(&self) -> Vec<Atom> {
234        vec![self.format]
235    }
236
237    fn prepare_drag_data(
238        &self,
239        data_in: &mut HashMap<String, Value>,
240    ) -> Vec<Box<dyn DragDataSetter>> {
241        vec![Box::new(FallthroughDragDataSetter {
242            values: take(data_in),
243            format: self.format,
244        })]
245    }
246}
247
248struct FallthroughDragDataSetter {
249    values: HashMap<String, Value>,
250    format: Atom,
251}
252
253impl DragDataSetter for FallthroughDragDataSetter {
254    fn set(&self, selection_data: &SelectionData) {
255        let codec: &'static dyn MessageCodec<Value> = &StandardMethodCodec;
256        let mut map = HashMap::new();
257        for e in &self.values {
258            map.insert(Value::String(e.0.into()), e.1.clone());
259        }
260        let data = codec.encode_message(&Value::Map(map));
261        selection_data.set(&self.format, 0, &data);
262    }
263
264    fn data_formats(&self) -> Vec<Atom> {
265        vec![self.format]
266    }
267}