Skip to main content

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}