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