nativeshell/shell/platform/linux/
drag_data.rs1use 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
196pub 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}