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