Skip to content

Commit 1dcc18b

Browse files
authored
Add file_list to Get interface (#179)
1 parent bbd06b4 commit 1dcc18b

File tree

8 files changed

+127
-10
lines changed

8 files changed

+127
-10
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ windows-sys = { version = ">=0.52.0, <0.60.0", optional = true, features = [
4444
"Win32_System_Memory",
4545
"Win32_System_Ole",
4646
] }
47-
clipboard-win = "5.3.1"
47+
clipboard-win = { version = "5.3.1", features = ["std"] }
4848
log = "0.4"
4949
image = { version = "0.25", optional = true, default-features = false, features = [
5050
"png",
@@ -58,6 +58,7 @@ objc2-foundation = { version = "0.3.0", default-features = false, features = [
5858
"NSString",
5959
"NSEnumerator",
6060
"NSGeometry",
61+
"NSValue",
6162
] }
6263
objc2-app-kit = { version = "0.3.0", default-features = false, features = [
6364
"std",
@@ -88,6 +89,7 @@ image = { version = "0.25", optional = true, default-features = false, features
8889
"png",
8990
] }
9091
parking_lot = "0.12"
92+
percent-encoding = "2.3.1"
9193

9294
[[example]]
9395
name = "get_image"

src/lib.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and conditions of the chosen license apply to this file.
1010
#![warn(unreachable_pub)]
1111

1212
mod common;
13-
use std::borrow::Cow;
13+
use std::{borrow::Cow, path::PathBuf};
1414

1515
pub use common::Error;
1616
#[cfg(feature = "image-data")]
@@ -197,6 +197,11 @@ impl Get<'_> {
197197
pub fn html(self) -> Result<String, Error> {
198198
self.platform.html()
199199
}
200+
201+
/// Completes the "get" operation by fetching a list of file paths from the clipboard.
202+
pub fn file_list(self) -> Result<Vec<PathBuf>, Error> {
203+
self.platform.file_list()
204+
}
200205
}
201206

202207
/// A builder for an operation that sets a value to the clipboard.

src/platform/linux/mod.rs

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::{borrow::Cow, time::Instant};
1+
use std::{borrow::Cow, path::PathBuf, time::Instant};
22

33
#[cfg(feature = "wayland-data-control")]
44
use log::{trace, warn};
5+
use percent_encoding::percent_decode_str;
56

67
#[cfg(feature = "image-data")]
78
use crate::ImageData;
@@ -38,6 +39,15 @@ fn encode_as_png(image: &ImageData) -> Result<Vec<u8>, Error> {
3839
Ok(png_bytes)
3940
}
4041

42+
fn paths_from_uri_list(uri_list: String) -> Vec<PathBuf> {
43+
uri_list
44+
.lines()
45+
.filter_map(|s| s.strip_prefix("file://"))
46+
.filter_map(|s| percent_decode_str(s).decode_utf8().ok())
47+
.map(|decoded| PathBuf::from(decoded.as_ref()))
48+
.collect()
49+
}
50+
4151
/// Clipboard selection
4252
///
4353
/// Linux has a concept of clipboard "selections" which tend to be used in different contexts. This
@@ -130,6 +140,14 @@ impl<'clipboard> Get<'clipboard> {
130140
Clipboard::WlDataControl(clipboard) => clipboard.get_html(self.selection),
131141
}
132142
}
143+
144+
pub(crate) fn file_list(self) -> Result<Vec<PathBuf>, Error> {
145+
match self.clipboard {
146+
Clipboard::X11(clipboard) => clipboard.get_file_list(self.selection),
147+
#[cfg(feature = "wayland-data-control")]
148+
Clipboard::WlDataControl(clipboard) => clipboard.get_file_list(self.selection),
149+
}
150+
}
133151
}
134152

135153
/// Linux-specific extensions to the [`Get`](super::Get) builder.
@@ -330,3 +348,28 @@ impl ClearExtLinux for crate::Clear<'_> {
330348
self.platform.clear_inner(selection)
331349
}
332350
}
351+
352+
#[cfg(test)]
353+
mod tests {
354+
use super::*;
355+
356+
#[test]
357+
fn test_decoding_uri_list() {
358+
// Test that paths_from_uri_list correctly decodes
359+
// differents percent encoded characters
360+
let file_list = vec![
361+
"file:///tmp/bar.log",
362+
"file:///tmp/test%5C.txt",
363+
"file:///tmp/foo%3F.png",
364+
"file:///tmp/white%20space.txt",
365+
];
366+
367+
let paths = vec![
368+
PathBuf::from("/tmp/bar.log"),
369+
PathBuf::from("/tmp/test\\.txt"),
370+
PathBuf::from("/tmp/foo?.png"),
371+
PathBuf::from("/tmp/white space.txt"),
372+
];
373+
assert_eq!(paths_from_uri_list(file_list.join("\n")), paths);
374+
}
375+
}

src/platform/linux/wayland.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use std::borrow::Cow;
2-
use std::io::Read;
1+
use std::{borrow::Cow, io::Read, path::PathBuf};
32

43
use wl_clipboard_rs::{
54
copy::{self, Error as CopyError, MimeSource, MimeType, Options, Source},
@@ -9,7 +8,7 @@ use wl_clipboard_rs::{
98

109
#[cfg(feature = "image-data")]
1110
use super::encode_as_png;
12-
use super::{into_unknown, LinuxClipboardKind, WaitConfig};
11+
use super::{into_unknown, paths_from_uri_list, LinuxClipboardKind, WaitConfig};
1312
use crate::common::Error;
1413
#[cfg(feature = "image-data")]
1514
use crate::common::ImageData;
@@ -182,4 +181,12 @@ impl Clipboard {
182181
opts.copy(source, MimeType::Specific(MIME_PNG.into())).map_err(into_unknown)?;
183182
Ok(())
184183
}
184+
185+
pub(crate) fn get_file_list(
186+
&mut self,
187+
selection: LinuxClipboardKind,
188+
) -> Result<Vec<PathBuf>, Error> {
189+
self.string_for_mime(selection, paste::MimeType::Specific("text/uri-list"))
190+
.map(paths_from_uri_list)
191+
}
185192
}

src/platform/linux/x11.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::{
1616
borrow::Cow,
1717
cell::RefCell,
1818
collections::{hash_map::Entry, HashMap},
19+
path::PathBuf,
1920
sync::{
2021
atomic::{AtomicBool, Ordering},
2122
Arc,
@@ -44,7 +45,7 @@ use x11rb::{
4445

4546
#[cfg(feature = "image-data")]
4647
use super::encode_as_png;
47-
use super::{into_unknown, LinuxClipboardKind, WaitConfig};
48+
use super::{into_unknown, paths_from_uri_list, LinuxClipboardKind, WaitConfig};
4849
#[cfg(feature = "image-data")]
4950
use crate::ImageData;
5051
use crate::{common::ScopeGuard, Error};
@@ -77,6 +78,7 @@ x11rb::atom_manager! {
7778
TEXT_MIME_UNKNOWN: b"text/plain",
7879

7980
HTML: b"text/html",
81+
URI_LIST: b"text/uri-list",
8082

8183
PNG_MIME: b"image/png",
8284

@@ -929,6 +931,14 @@ impl Clipboard {
929931
let data = vec![ClipboardData { bytes: encoded, format: self.inner.atoms.PNG_MIME }];
930932
self.inner.write(data, selection, wait)
931933
}
934+
935+
pub(crate) fn get_file_list(&self, selection: LinuxClipboardKind) -> Result<Vec<PathBuf>> {
936+
let result = self.inner.read(&[self.inner.atoms.URI_LIST], selection)?;
937+
938+
String::from_utf8(result.bytes)
939+
.map_err(|_| Error::ConversionFailure)
940+
.map(paths_from_uri_list)
941+
}
932942
}
933943

934944
impl Drop for Clipboard {

src/platform/osx.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ use objc2::{
1717
runtime::ProtocolObject,
1818
ClassType,
1919
};
20-
use objc2_app_kit::{NSPasteboard, NSPasteboardTypeHTML, NSPasteboardTypeString};
21-
use objc2_foundation::{ns_string, NSArray, NSString};
20+
use objc2_app_kit::{
21+
NSPasteboard, NSPasteboardTypeHTML, NSPasteboardTypeString,
22+
NSPasteboardURLReadingFileURLsOnlyKey,
23+
};
24+
use objc2_foundation::{ns_string, NSArray, NSDictionary, NSNumber, NSString, NSURL};
2225
use std::{
2326
borrow::Cow,
2427
panic::{RefUnwindSafe, UnwindSafe},
28+
path::PathBuf,
2529
};
2630

2731
/// Returns an NSImage object on success.
@@ -236,6 +240,35 @@ impl<'clipboard> Get<'clipboard> {
236240
bytes: rgba.into_raw().into(),
237241
})
238242
}
243+
244+
pub(crate) fn file_list(self) -> Result<Vec<PathBuf>, Error> {
245+
autoreleasepool(|_| {
246+
let class_array = NSArray::from_slice(&[NSURL::class()]);
247+
let options = NSDictionary::from_slices(
248+
&[unsafe { NSPasteboardURLReadingFileURLsOnlyKey }],
249+
&[NSNumber::new_bool(true).as_ref()],
250+
);
251+
let objects = unsafe {
252+
self.clipboard
253+
.pasteboard
254+
.readObjectsForClasses_options(&class_array, Some(&options))
255+
};
256+
257+
objects
258+
.map(|array| {
259+
array
260+
.iter()
261+
.filter_map(|obj| {
262+
obj.downcast::<NSURL>().ok().and_then(|url| {
263+
unsafe { url.path() }.map(|p| PathBuf::from(p.to_string()))
264+
})
265+
})
266+
.collect::<Vec<_>>()
267+
})
268+
.filter(|file_list| !file_list.is_empty())
269+
.ok_or(Error::ContentNotAvailable)
270+
})
271+
}
239272
}
240273

241274
pub(crate) struct Set<'clipboard> {

src/platform/windows.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ and conditions of the chosen license apply to this file.
1111
#[cfg(feature = "image-data")]
1212
use crate::common::ImageData;
1313
use crate::common::{private, Error};
14-
use std::{borrow::Cow, marker::PhantomData, thread, time::Duration};
14+
use std::{borrow::Cow, marker::PhantomData, path::PathBuf, thread, time::Duration};
1515

1616
#[cfg(feature = "image-data")]
1717
mod image_data {
@@ -635,6 +635,16 @@ impl<'clipboard> Get<'clipboard> {
635635

636636
image_data::read_cf_dibv5(&data)
637637
}
638+
639+
pub(crate) fn file_list(self) -> Result<Vec<PathBuf>, Error> {
640+
let _clipboard_assertion = self.clipboard?;
641+
642+
let mut file_list = Vec::new();
643+
clipboard_win::raw::get_file_list_path(&mut file_list)
644+
.map_err(|_| Error::ContentNotAvailable)?;
645+
646+
Ok(file_list)
647+
}
638648
}
639649

640650
pub(crate) struct Set<'clipboard> {

0 commit comments

Comments
 (0)