ui/drop.rs
1// drop -- (K1) dropped files and other content for apps, the clipboard format probe, the
2// headless image clipboard; the command lines of later starts (AF_SINGLE_INSTANCE)
3// [src/ui/ui_drop.s]
4//
5// open_take() -> Vec<Request> (UIC_OPEN, arg = their count) the working folder and
6// arguments of every later start of the app since the last
7// call; Request::paths() gives the files, which the app may
8// read and write (fs.user); the main window comes forward
9//
10// drop_count() -> n files of the last drop (UIC_DROP, arg = window id; or
11// DS_DROP of a drop target, dnd.rs)
12// drop_path(i) -> Option<&str> path i; valid until the next drop arrives
13// drop_list() -> &[String] every path of the last drop
14// drop_data() -> &DragData everything the last drop carried: paths, text, URLs, HTML,
15// an image file (PNG, or BMP from a DIB), the app's own kind
16// and bytes; drop_types() -> its DT_* kinds, drop_text()
17// drop_pos() -> (x, y) drop position in DIP of the window it happened in
18// drop_window() -> id that window's app id (0 = main window)
19// clip_has(fmt) -> bool the clipboard offers CLIP_TEXT / CLIP_PNG / CLIP_IMAGE
20// (headless runs: the in-process clipboard)
21// Which kinds a window takes outside its drop targets: drop_accept (dnd.rs, default DT_FILES).
22// Internal:
23// drop_receive(ev) -> taken E_DROP: the platform's pending drop (runtime.rs) onto the
24// drop target at the point (true) or for UIC_DROP (false)
25// drop_inject(data) script "drop": the content of the next E_DROP
26// hclip_img_set(fmt, data) headless CLIP_PNG (bytes) / CLIP_IMAGE (a CLIPIMG
27// record: png then bmp, see clipimg.rs): PNG + BMP copies
28// hclip_img_get(fmt) CLIP_PNG: the PNG; CLIP_IMAGE: the PNG, else the BMP
29// hclip_img_clear() hclip_img_has(fmt)
30
31use alloc::string::String;
32use alloc::vec::Vec;
33
34use super::consts::*;
35use super::Ui;
36use plat::{DragData, Event};
37
38/// drop and headless image clipboard state
39#[derive(Default)]
40pub(crate) struct S {
41 pub(crate) list: Vec<String>,
42 pub(crate) inj: Option<DragData>,
43 pub(crate) hi_png: Option<Vec<u8>>,
44 pub(crate) hi_bmp: Option<Vec<u8>>,
45 pub(crate) x: i32,
46 pub(crate) y: i32,
47 pub(crate) win: u32,
48}
49
50impl Ui {
51 /// ui_drop_count: files of the last drop
52 pub fn drop_count(&self) -> u32 {
53 self.dr.list.len() as u32
54 }
55
56 /// ui_drop_path(i): path i of the last drop (None when out of range)
57 pub fn drop_path(&self, i: u32) -> Option<&str> {
58 self.dr.list.get(i as usize).map(|s| s.as_str())
59 }
60
61 /// AF_SINGLE_INSTANCE: the command lines of later starts that arrived (UIC_OPEN), oldest
62 /// first; each call takes them
63 pub fn open_take(&mut self) -> Vec<plat::instance::Request> {
64 core::mem::take(&mut self.open_reqs)
65 }
66
67 /// E_WAKE: requests of later starts -> their files granted (fs.user), the main window
68 /// brought forward, App::command(UIC_OPEN, pending requests)
69 pub(crate) fn open_poll(&mut self) {
70 let reqs = plat::instance::take();
71 if reqs.is_empty() {
72 return;
73 }
74 for p in reqs.iter().flat_map(|r| r.paths()).filter(|p| !p.contains("://") && grantable(p)) {
75 super::perm::grant_user(&p);
76 }
77 self.bring_to_front();
78 self.open_reqs.extend(reqs);
79 let n = self.open_reqs.len() as u32;
80 self.cmd_run(UIC_OPEN, n);
81 }
82
83 /// ui_drop_list: every path of the last drop
84 pub fn drop_list(&self) -> &[String] {
85 &self.dr.list
86 }
87
88 /// ui_drop_pos: drop position in DIP of the window it happened in
89 pub fn drop_pos(&self) -> (i32, i32) {
90 (self.dr.x, self.dr.y)
91 }
92
93 /// ui_drop_window: the app id of the window of the last drop (0 = main window)
94 pub fn drop_window(&self) -> u32 {
95 self.dr.win
96 }
97
98 /// everything the last drop carried (paths, text, URLs, HTML, image, the app's own kind
99 /// and bytes; effects = the effect it was dropped with)
100 pub fn drop_data(&self) -> &DragData {
101 &self.dnd.dropped
102 }
103
104 /// the DT_* kinds of the last drop
105 pub fn drop_types(&self) -> u32 {
106 self.dnd.dropped.types()
107 }
108
109 /// the text of the last drop (None: it had none)
110 pub fn drop_text(&self) -> Option<&str> {
111 self.dnd.dropped.text.as_deref()
112 }
113
114 // drop_install(list, win, x_px, y_px): the new current list
115 fn drop_install(&mut self, list: Vec<String>, win: u32, x: i32, y: i32) {
116 self.dr.list = list;
117 self.dr.win = self.uiw_rec_id(win);
118 self.uiw_use(win); // DIP in that window
119 let x = if self.rtl { self.wp.rt.fb.w - 1 - x } else { x }; // logical (right-to-left)
120 self.dr.x = self.px2dip(x);
121 self.dr.y = self.px2dip(y);
122 self.uiw_use(0);
123 }
124
125 /// ui_drop_accept(ev) -> taken: E_DROP of window ev.win (UI index): the platform's drop
126 /// becomes current; a drop target at the point takes it (DS_DROP), else UIC_DROP follows
127 pub(crate) fn drop_receive(&mut self, ev: &Event) -> bool {
128 let data = if self.headless {
129 self.dr.inj.take().unwrap_or_default() // headless: the content of the script's "drop"
130 } else {
131 plat::drop_data_take()
132 };
133 super::perm::grant_all(&data.files); // the user dropped them (fs.user)
134 let win = ev.win as u32;
135 self.drop_install(data.files.clone(), win, ev.x, ev.y);
136 let cur = self.uiw_cur;
137 self.uiw_use(win);
138 let x = if self.rtl { self.wp.rt.fb.w - 1 - ev.x } else { ev.x }; // logical (right-to-left)
139 self.uiw_use(cur);
140 self.dnd_dropped(win, x, ev.y, data)
141 }
142
143 /// ui_drop_inject(data): (script) the next E_DROP gets this content
144 pub(crate) fn drop_inject(&mut self, data: DragData) {
145 self.dr.inj = Some(data);
146 }
147
148 /// ui_clip_has(fmt): the clipboard offers fmt (cheap: no data is transferred)
149 pub fn clip_has(&self, fmt: u32) -> bool {
150 if !self.headless {
151 return plat::clip_has(fmt);
152 }
153 if fmt == CLIP_TEXT {
154 return self.hclip_text_has();
155 }
156 if matches!(fmt, CLIP_HTML | CLIP_RTF | CLIP_DOCX) {
157 return self.m.edit.hclip_rich.iter().any(|e| e.0 == fmt);
158 }
159 self.hclip_img_has(fmt)
160 }
161
162 /// hclip_img_clear
163 pub(crate) fn hclip_img_clear(&mut self) {
164 self.dr.hi_png = None;
165 self.dr.hi_bmp = None;
166 }
167
168 /// hclip_img_has(fmt): a PNG is stored (CLIP_IMAGE: or a BMP)
169 pub(crate) fn hclip_img_has(&self, fmt: u32) -> bool {
170 self.dr.hi_png.is_some() || (fmt == CLIP_IMAGE && self.dr.hi_bmp.is_some())
171 }
172
173 /// hclip_img_set(fmt, data) -> 0 / -1: CLIP_PNG bytes, or CLIP_IMAGE: a CLIPIMG record
174 /// (clipimg::clipimg_record: u64 PNG length, PNG, u64 BMP length, BMP)
175 pub(crate) fn hclip_img_set(&mut self, fmt: u32, data: &[u8]) -> i32 {
176 match fmt {
177 CLIP_PNG => {
178 self.hclip_img_set_parts(Some(data), None);
179 0
180 }
181 CLIP_IMAGE => match super::clipimg::clipimg_parse(data) {
182 Some((png, bmp)) => {
183 self.hclip_img_set_parts(png, bmp);
184 0
185 }
186 None => {
187 self.hclip_text_clear();
188 self.hclip_img_clear();
189 -1
190 }
191 },
192 _ => {
193 self.hclip_text_clear();
194 self.hclip_img_clear();
195 -1
196 }
197 }
198 }
199
200 /// the stored image as PNG and / or BMP copies (one content at a time)
201 pub(crate) fn hclip_img_set_parts(&mut self, png: Option<&[u8]>, bmp: Option<&[u8]>) {
202 self.hclip_text_clear();
203 self.hclip_img_clear();
204 self.dr.hi_bmp = bmp.map(|b| b.to_vec());
205 self.dr.hi_png = png.map(|p| p.to_vec());
206 }
207
208 /// hclip_img_get(fmt): CLIP_PNG: the PNG; CLIP_IMAGE: the PNG, else the BMP (copies)
209 pub(crate) fn hclip_img_get(&self, fmt: u32) -> Option<Vec<u8>> {
210 if fmt != CLIP_PNG && fmt != CLIP_IMAGE {
211 return None;
212 }
213 if let Some(p) = &self.dr.hi_png {
214 return Some(p.clone());
215 }
216 if fmt == CLIP_IMAGE {
217 return self.dr.hi_bmp.clone();
218 }
219 None
220 }
221}
222
223/// whether a command-line path of a later start may be granted: no `..` parts and not a
224/// drive / file system root (those would open the whole disk)
225fn grantable(p: &str) -> bool {
226 let rest = p.strip_prefix("\\\\?\\").unwrap_or(p);
227 let rest = match rest.as_bytes() {
228 [d, b':', ..] if d.is_ascii_alphabetic() => &rest[2..],
229 _ => rest,
230 };
231 let rest = rest.trim_matches(|c| c == '/' || c == '\\');
232 !rest.is_empty() && !p.split(|c| c == '/' || c == '\\').any(|s| s == "..")
233}