Implemented set_image on macOS.

This commit is contained in:
Artur Kovacs 2020-07-13 16:21:57 +02:00
parent 1b363cd7a2
commit e4545b2231
11 changed files with 1122 additions and 276 deletions

View file

@ -16,6 +16,7 @@ byteorder = "1.3"
objc = "0.2"
objc_id = "0.1"
objc-foundation = "0.1"
core-graphics = "0.21"
image = { version = "0.23", default-features = false, features = ["tiff"] }
[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))'.dependencies]

View file

@ -1,12 +1,12 @@
extern crate clipboard;
use clipboard::ClipboardProvider;
use clipboard::ClipboardContext;
use clipboard::ClipboardProvider;
fn main() {
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
let the_string = "Hello, world!";
let the_string = "Hello, world!";
ctx.set_text(the_string.to_owned()).unwrap();
ctx.set_text(the_string.to_owned()).unwrap();
}

View file

@ -1,11 +1,11 @@
extern crate clipboard;
use clipboard::{ClipboardProvider, ClipboardContext, ImageData};
use clipboard::{ClipboardContext, ClipboardProvider, ImageData};
fn main() {
let mut ctx = ClipboardContext::new().unwrap();
let mut ctx = ClipboardContext::new().unwrap();
#[rustfmt::skip]
let img_data = ImageData {
width: 2,
height: 2,
@ -14,8 +14,8 @@ fn main() {
100, 255, 100, 100,
100, 100, 255, 100,
0, 0, 0, 255,
].as_ref().into()
].as_ref().into(),
};
ctx.set_image(img_data).unwrap();
ctx.set_image(img_data).unwrap();
}

View file

@ -1,19 +1,19 @@
extern crate clipboard;
use clipboard::ClipboardProvider;
#[cfg(target_os = "linux")]
use clipboard::x11_clipboard::{X11ClipboardContext, Primary};
use clipboard::x11_clipboard::{Primary, X11ClipboardContext};
use clipboard::ClipboardProvider;
#[cfg(target_os = "linux")]
fn main() {
let mut ctx: X11ClipboardContext<Primary> = ClipboardProvider::new().unwrap();
let mut ctx: X11ClipboardContext<Primary> = ClipboardProvider::new().unwrap();
let the_string = "Hello, world!";
let the_string = "Hello, world!";
ctx.set_text(the_string.to_owned()).unwrap();
ctx.set_text(the_string.to_owned()).unwrap();
}
#[cfg(not(target_os = "linux"))]
fn main() {
println!("Primary selection is only available under linux!");
println!("Primary selection is only available under linux!");
}

767
osx_clipboard_expanded.rs Normal file
View file

@ -0,0 +1,767 @@
#[cfg(target_os = "macos")]
pub mod osx_clipboard {
use common::*;
use core_graphics::color_space::{CGColorSpace, CGColorSpaceRef};
use core_graphics::image::{CGImage, CGImageAlphaInfo};
use core_graphics::{
base::{kCGRenderingIntentDefault, CGFloat},
data_provider::{CGDataProvider, CustomData},
};
use objc::runtime::{Class, Object};
use objc_foundation::{INSArray, INSObject, INSString};
use objc_foundation::{NSArray, NSDictionary, NSObject, NSString};
use objc_id::{Id, Owned};
use std::error::Error;
use std::{mem::transmute, ops::Deref};
pub struct OSXClipboardContext {
pasteboard: Id<Object>,
}
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
#[repr(C)]
pub struct NSSize {
pub width: CGFloat,
pub height: CGFloat,
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::marker::Copy for NSSize {}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::clone::Clone for NSSize {
#[inline]
fn clone(&self) -> NSSize {
{
let _: ::core::clone::AssertParamIsClone<CGFloat>;
let _: ::core::clone::AssertParamIsClone<CGFloat>;
*self
}
}
}
struct PixelArray {
data: Vec<u8>,
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::fmt::Debug for PixelArray {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match *self {
PixelArray {
data: ref __self_0_0,
} => {
let mut debug_trait_builder = f.debug_struct("PixelArray");
let _ = debug_trait_builder.field("data", &&(*__self_0_0));
debug_trait_builder.finish()
}
}
}
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::clone::Clone for PixelArray {
#[inline]
fn clone(&self) -> PixelArray {
match *self {
PixelArray {
data: ref __self_0_0,
} => PixelArray {
data: ::core::clone::Clone::clone(&(*__self_0_0)),
},
}
}
}
impl CustomData for PixelArray {
unsafe fn ptr(&self) -> *const u8 {
self.data.as_ptr()
}
unsafe fn len(&self) -> usize {
self.data.len()
}
}
/// Returns an NSImage object on success.
fn image_from_pixels(
pixels: Vec<u8>,
width: usize,
height: usize,
) -> Result<Id<NSObject>, Box<dyn Error>> {
let colorspace = CGColorSpace::create_device_rgb();
let bitmap_info: u32 = CGImageAlphaInfo::CGImageAlphaLast as u32;
let pixel_data: Box<Box<dyn CustomData>> = Box::new(Box::new(PixelArray { data: pixels }));
let provider = unsafe { CGDataProvider::from_custom_data(pixel_data) };
let rendering_intent = kCGRenderingIntentDefault;
let cg_image = CGImage::new(
width,
height,
8,
32,
4 * width,
&colorspace,
bitmap_info,
&provider,
false,
rendering_intent,
);
let NSImage_class = Class::get("NSImage").ok_or(err("Class::get(\"NSImage\")"))?;
let size = NSSize {
width: width as CGFloat,
height: height as CGFloat,
};
let image: Id<NSObject> = unsafe {
Id::from_ptr({
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("alloc\u{0}")
}
};
let result;
match ::objc::__send_message(&*NSImage_class, sel, ()) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
})
};
let ptr: *const std::ffi::c_void = unsafe { transmute(&*cg_image) };
let a = &*cg_image;
let image: Id<NSObject> = unsafe {
Id::from_ptr({
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("initWithCGImage:size:\u{0}")
}
};
let result;
match ::objc::__send_message(&*image, sel, (cg_image, &size)) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
})
};
Ok(image)
}
impl ClipboardProvider for OSXClipboardContext {
fn new() -> Result<OSXClipboardContext, Box<Error>> {
let cls = match Class::get("NSPasteboard").ok_or(err("Class::get(\"NSPasteboard\")")) {
::core::result::Result::Ok(val) => val,
::core::result::Result::Err(err) => {
return ::core::result::Result::Err(::core::convert::From::from(err));
}
};
let pasteboard: *mut Object = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("generalPasteboard\u{0}")
}
};
let result;
match ::objc::__send_message(&*cls, sel, ()) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
}
};
if pasteboard.is_null() {
return Err(err("NSPasteboard#generalPasteboard returned null"));
}
let pasteboard: Id<Object> = unsafe { Id::from_ptr(pasteboard) };
Ok(OSXClipboardContext {
pasteboard: pasteboard,
})
}
fn get_text(&mut self) -> Result<String, Box<Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let classes: Id<NSArray<NSObject, Owned>> =
NSArray::from_vec(<[_]>::into_vec(box [string_class]));
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let string_array: Id<NSArray<NSString>> = unsafe {
let obj: *mut NSArray<NSString> = {
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("readObjectsForClasses:options:\u{0}")
}
};
let result;
match ::objc::__send_message(&*self.pasteboard, sel, (&*classes, &*options)) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
};
if obj.is_null() {
return Err(err(
"pasteboard#readObjectsForClasses:options: returned null",
));
}
Id::from_ptr(obj)
};
if string_array.count() == 0 {
Err(err(
"pasteboard#readObjectsForClasses:options: returned empty",
))
} else {
Ok(string_array[0].as_str().to_owned())
}
}
fn set_text(&mut self, data: String) -> Result<(), Box<Error>> {
let string_array = NSArray::from_vec(<[_]>::into_vec(box [NSString::from_str(&data)]));
let _: usize = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("clearContents\u{0}")
}
};
let result;
match ::objc::__send_message(&*self.pasteboard, sel, ()) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
}
};
let success: bool = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("writeObjects:\u{0}")
}
};
let result;
match ::objc::__send_message(&*self.pasteboard, sel, (string_array,)) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
}
};
return if success {
Ok(())
} else {
Err(err("NSPasteboard#writeObjects: returned false"))
};
}
fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let image_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSImage")) };
unsafe { transmute(cls) }
};
let url_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSURL")) };
unsafe { transmute(cls) }
};
let classes = <[_]>::into_vec(box [url_class, image_class, string_class]);
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(classes);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let contents: Id<NSArray<NSObject>> = unsafe {
let obj: *mut NSArray<NSObject> = {
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("readObjectsForClasses:options:\u{0}")
}
};
let result;
match ::objc::__send_message(&*self.pasteboard, sel, (&*classes, &*options)) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
};
if obj.is_null() {
return Err(err(
"pasteboard#readObjectsForClasses:options: returned null",
));
}
Id::from_ptr(obj)
};
if contents.count() == 0 {
Ok(None)
} else {
let obj = &contents[0];
if obj.is_kind_of(Class::get("NSString").unwrap()) {
let s: &NSString = unsafe { transmute(obj) };
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else if obj.is_kind_of(Class::get("NSImage").unwrap()) {
let tiff: &NSArray<NSObject> = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL
.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _,
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("TIFFRepresentation\u{0}")
}
};
let result;
match ::objc::__send_message(&*obj, sel, ()) {
Err(s) => {
::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
))
}
Ok(r) => result = r,
}
result
}
};
let len: usize = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL
.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _,
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("length\u{0}")
}
};
let result;
match ::objc::__send_message(&*tiff, sel, ()) {
Err(s) => {
::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
))
}
Ok(r) => result = r,
}
result
}
};
let bytes: *const u8 = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL
.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _,
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("bytes\u{0}")
}
};
let result;
match ::objc::__send_message(&*tiff, sel, ()) {
Err(s) => {
::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
))
}
Ok(r) => result = r,
}
result
}
};
let vec = unsafe { std::slice::from_raw_parts(bytes, len) };
Ok(Some(ClipboardContent::Tiff(vec.into())))
} else if obj.is_kind_of(Class::get("NSURL").unwrap()) {
let s: &NSString = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL
.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _,
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("absoluteString\u{0}")
}
};
let result;
match ::objc::__send_message(&*obj, sel, ()) {
Err(s) => {
::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
))
}
Ok(r) => result = r,
}
result
}
};
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else {
Err(err(
"pasteboard#readObjectsForClasses:options: returned unknown class",
))
}
}
}
fn get_image(&mut self) -> Result<ImageData, Box<dyn Error>> {
Err("Not implemented".into())
}
fn set_image(&mut self, data: ImageData) -> Result<(), Box<dyn Error>> {
let pixels = data.bytes.into();
let image = image_from_pixels(pixels, data.width, data.height)?;
let objects: Id<NSArray<NSObject, Owned>> =
NSArray::from_vec(<[_]>::into_vec(box [image]));
let _: usize = unsafe {
{
let sel = {
{
#[allow(deprecated)]
#[inline(always)]
fn register_sel(name: &str) -> ::objc::runtime::Sel {
unsafe {
static SEL: ::std::sync::atomic::AtomicUsize =
::std::sync::atomic::ATOMIC_USIZE_INIT;
let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed)
as *const ::std::os::raw::c_void;
if ptr.is_null() {
let sel = ::objc::runtime::sel_registerName(
name.as_ptr() as *const _
);
SEL.store(
sel.as_ptr() as usize,
::std::sync::atomic::Ordering::Relaxed,
);
sel
} else {
::objc::runtime::Sel::from_ptr(ptr)
}
}
}
register_sel("writeObjects:\u{0}")
}
};
let result;
match ::objc::__send_message(&*self.pasteboard, sel, (&*objects,)) {
Err(s) => ::std::rt::begin_panic_fmt(&::core::fmt::Arguments::new_v1(
&[""],
&match (&s,) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
)),
Ok(r) => result = r,
}
result
}
};
Ok(())
}
}
#[inline]
pub fn class(name: &str) -> *mut Class {
unsafe { transmute(Class::get(name)) }
}
}

4
rustfmt.toml Normal file
View file

@ -0,0 +1,4 @@
hard_tabs=true
use_field_init_shorthand=true
use_small_heuristics="Max"
use_try_shorthand=true

View file

@ -14,20 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
use std::error::Error;
use std::borrow::Cow;
use std::error::Error;
pub fn err(s: &str) -> Box<Error> {
Box::<Error + Send + Sync>::from(s)
pub fn err(s: &str) -> Box<dyn Error> {
Box::<dyn Error + Send + Sync>::from(s)
}
pub enum ClipboardContent {
Utf8(String),
Tiff(Vec<u8>),
// TODO: extend this enum by more types
// Url, RichText, ....
#[doc(hidden)]
__Nonexhaustive,
Utf8(String),
Tiff(Vec<u8>),
// TODO: extend this enum by more types
// Url, RichText, ....
#[doc(hidden)]
__Nonexhaustive,
}
/// Stores pixel data of an image.
@ -38,14 +38,14 @@ pub enum ClipboardContent {
///
/// The pixels are stored in row-major order meaning that the second pixel
/// in `bytes` corresponds to the pixel form the first row and second column.
///
///
/// Assigning a 2*1 image would for example look like this
/// ```
/// use std::borrow::Cow;
/// let bytes = [
/// // A red pixel
/// 255, 0, 0, 255,
///
///
/// // A green pixel
/// 0, 255, 0, 255,
/// ];
@ -56,22 +56,22 @@ pub enum ClipboardContent {
/// };
/// ```
pub struct ImageData<'a> {
pub width: usize,
pub height: usize,
pub bytes: Cow<'a, [u8]>,
pub width: usize,
pub height: usize,
pub bytes: Cow<'a, [u8]>,
}
/// Trait for clipboard access
pub trait ClipboardProvider: Sized {
/// Create a context with which to access the clipboard
// TODO: consider replacing Box<Error> with an associated type?
fn new() -> Result<Self, Box<dyn Error>>;
/// Method to get the clipboard contents as a String
fn get_text(&mut self) -> Result<String, Box<dyn Error>>;
/// Method to set the clipboard contents as a String
fn set_text(&mut self, text: String) -> Result<(), Box<dyn Error>>;
/// Method to get clipboard contents not necessarily string
fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<dyn Error>>;
fn get_image(&mut self) -> Result<ImageData, Box<dyn Error>>;
fn set_image(&mut self, data: ImageData) -> Result<(), Box<dyn Error>>;
/// Create a context with which to access the clipboard
// TODO: consider replacing Box<Error> with an associated type?
fn new() -> Result<Self, Box<dyn Error>>;
/// Method to get the clipboard contents as a String
fn get_text(&mut self) -> Result<String, Box<dyn Error>>;
/// Method to set the clipboard contents as a String
fn set_text(&mut self, text: String) -> Result<(), Box<dyn Error>>;
/// Method to get clipboard contents not necessarily string
fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<dyn Error>>;
fn get_image(&mut self) -> Result<ImageData, Box<dyn Error>>;
fn set_image(&mut self, data: ImageData) -> Result<(), Box<dyn Error>>;
}

View file

@ -19,48 +19,50 @@ limitations under the License.
#![crate_type = "dylib"]
#![crate_type = "rlib"]
#[cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))]
#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))]
extern crate x11_clipboard as x11_clipboard_crate;
#[cfg(windows)]
extern crate byteorder;
#[cfg(windows)]
extern crate clipboard_win;
#[cfg(windows)]
extern crate image;
#[cfg(windows)]
extern crate byteorder;
#[cfg(target_os="macos")]
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
#[cfg(target_os="macos")]
extern crate objc_id;
#[cfg(target_os="macos")]
#[cfg(target_os = "macos")]
extern crate core_graphics;
#[cfg(target_os = "macos")]
extern crate objc_foundation;
#[cfg(target_os = "macos")]
extern crate objc_id;
mod common;
pub use common::ClipboardProvider;
pub use common::ClipboardContent;
pub use common::ClipboardProvider;
pub use common::ImageData;
#[cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))]
#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))]
pub mod x11_clipboard;
#[cfg(windows)]
pub mod windows_clipboard;
#[cfg(target_os="macos")]
#[cfg(target_os = "macos")]
pub mod osx_clipboard;
#[cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))]
#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))]
pub type ClipboardContext = x11_clipboard::X11ClipboardContext;
#[cfg(windows)]
pub type ClipboardContext = windows_clipboard::WindowsClipboardContext;
#[cfg(target_os="macos")]
#[cfg(target_os = "macos")]
pub type ClipboardContext = osx_clipboard::OSXClipboardContext;
#[test]
fn test_text() {
let mut ctx = ClipboardContext::new().unwrap();
ctx.set_text("some string".to_owned()).unwrap();
assert!(ctx.get_text().unwrap() == "some string");
let mut ctx = ClipboardContext::new().unwrap();
ctx.set_text("some string".to_owned()).unwrap();
assert!(ctx.get_text().unwrap() == "some string");
}

View file

@ -15,152 +15,227 @@ limitations under the License.
*/
use common::*;
use objc::runtime::{Object, Class};
use objc_foundation::{INSArray, INSString, INSObject};
use objc_foundation::{NSArray, NSDictionary, NSString, NSObject};
use core_graphics::color_space::CGColorSpace;
use core_graphics::image::CGImage;
use core_graphics::{
base::{kCGBitmapByteOrderDefault, kCGImageAlphaLast, kCGRenderingIntentDefault, CGFloat},
data_provider::{CGDataProvider, CustomData},
};
use objc::runtime::{Class, Object, BOOL, NO};
use objc_foundation::{INSArray, INSObject, INSString};
use objc_foundation::{NSArray, NSDictionary, NSObject, NSString};
use objc_id::{Id, Owned};
use std::error::Error;
use std::mem::transmute;
pub struct OSXClipboardContext {
pasteboard: Id<Object>,
pasteboard: Id<Object>,
}
// required to bring NSPasteboard into the path of the class-resolver
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct NSSize {
pub width: CGFloat,
pub height: CGFloat,
}
#[derive(Debug, Clone)]
struct PixelArray {
data: Vec<u8>,
}
impl CustomData for PixelArray {
unsafe fn ptr(&self) -> *const u8 {
self.data.as_ptr()
}
unsafe fn len(&self) -> usize {
self.data.len()
}
}
/// Returns an NSImage object on success.
fn image_from_pixels(
pixels: Vec<u8>,
width: usize,
height: usize,
) -> Result<Id<NSObject>, Box<dyn Error>> {
let colorspace = CGColorSpace::create_device_rgb();
let bitmap_info: u32 = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
let pixel_data: Box<Box<dyn CustomData>> = Box::new(Box::new(PixelArray { data: pixels }));
let provider = unsafe { CGDataProvider::from_custom_data(pixel_data) };
let rendering_intent = kCGRenderingIntentDefault;
let cg_image = CGImage::new(
width,
height,
8,
32,
4 * width,
&colorspace,
bitmap_info,
&provider,
false,
rendering_intent,
);
let size = NSSize { width: width as CGFloat, height: height as CGFloat };
let nsimage_class = Class::get("NSImage").ok_or(err("Class::get(\"NSImage\")"))?;
let image: Id<NSObject> = unsafe { Id::from_ptr(msg_send![nsimage_class, alloc]) };
let () = unsafe { msg_send![image, initWithCGImage:cg_image size:size] };
Ok(image)
}
impl ClipboardProvider for OSXClipboardContext {
fn new() -> Result<OSXClipboardContext, Box<Error>> {
let cls = try!(Class::get("NSPasteboard").ok_or(err("Class::get(\"NSPasteboard\")")));
let pasteboard: *mut Object = unsafe { msg_send![cls, generalPasteboard] };
if pasteboard.is_null() {
return Err(err("NSPasteboard#generalPasteboard returned null"));
}
let pasteboard: Id<Object> = unsafe { Id::from_ptr(pasteboard) };
Ok(OSXClipboardContext { pasteboard: pasteboard })
}
fn get_text(&mut self) -> Result<String, Box<Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(vec![string_class]);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let string_array: Id<NSArray<NSString>> = unsafe {
let obj: *mut NSArray<NSString> =
msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
if obj.is_null() {
return Err(err("pasteboard#readObjectsForClasses:options: returned null"));
}
Id::from_ptr(obj)
};
if string_array.count() == 0 {
Err(err("pasteboard#readObjectsForClasses:options: returned empty"))
} else {
Ok(string_array[0].as_str().to_owned())
}
}
fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let image_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSImage")) };
unsafe { transmute(cls) }
};
let url_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSURL")) };
unsafe { transmute(cls) }
};
let classes = vec![url_class, image_class, string_class];
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(classes);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let contents: Id<NSArray<NSObject>> = unsafe {
let obj: *mut NSArray<NSObject> =
msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
if obj.is_null() {
return Err(err("pasteboard#readObjectsForClasses:options: returned null"));
}
Id::from_ptr(obj)
};
if contents.count() == 0 {
Ok(None)
} else {
let obj = &contents[0];
if obj.is_kind_of(Class::get("NSString").unwrap()) {
let s: &NSString = unsafe { transmute(obj) };
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else if obj.is_kind_of(Class::get("NSImage").unwrap()) {
let tiff: &NSArray<NSObject> = unsafe { msg_send![obj, TIFFRepresentation] };
let len: usize = unsafe { msg_send![tiff, length] };
let bytes: *const u8 = unsafe { msg_send![tiff, bytes] };
let vec = unsafe { std::slice::from_raw_parts(bytes, len) };
// Here we copy the entire &[u8] into a new owned `Vec`
// Is there another way that doesn't copy multiple megabytes?
Ok(Some(ClipboardContent::Tiff(vec.into())))
} else if obj.is_kind_of(Class::get("NSURL").unwrap()) {
let s: &NSString = unsafe { msg_send![obj, absoluteString] };
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else {
// let cls: &Class = unsafe { msg_send![obj, class] };
// println!("{}", cls.name());
Err(err("pasteboard#readObjectsForClasses:options: returned unknown class"))
}
}
}
fn get_image(&mut self) -> Result<ImageData, Box<Error>> {
let image_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSImage")) };
unsafe { transmute(cls) }
};
let classes = vec![image_class];
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(classes);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let contents: Id<NSArray<NSObject>> = unsafe {
let obj: *mut NSArray<NSObject> =
msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
if obj.is_null() {
return Err(err("pasteboard#readObjectsForClasses:options: returned null"));
}
Id::from_ptr(obj)
};
if contents.count() == 0 {
Ok(None)
} else {
let obj = &contents[0];
if obj.is_kind_of(Class::get("NSString").unwrap()) {
let s: &NSString = unsafe { transmute(obj) };
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else if obj.is_kind_of(Class::get("NSImage").unwrap()) {
let tiff: &NSArray<NSObject> = unsafe { msg_send![obj, TIFFRepresentation] };
let len: usize = unsafe { msg_send![tiff, length] };
let bytes: *const u8 = unsafe { msg_send![tiff, bytes] };
let vec = unsafe { std::slice::from_raw_parts(bytes, len) };
// Here we copy the entire &[u8] into a new owned `Vec`
// Is there another way that doesn't copy multiple megabytes?
Ok(Some(ClipboardContent::Tiff(vec.into())))
} else if obj.is_kind_of(Class::get("NSURL").unwrap()) {
let s: &NSString = unsafe { msg_send![obj, absoluteString] };
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else {
// let cls: &Class = unsafe { msg_send![obj, class] };
// println!("{}", cls.name());
Err(err("pasteboard#readObjectsForClasses:options: returned unknown class"))
}
}
}
fn set_text(&mut self, data: String) -> Result<(), Box<Error>> {
let string_array = NSArray::from_vec(vec![NSString::from_str(&data)]);
let _: usize = unsafe { msg_send![self.pasteboard, clearContents] };
let success: bool = unsafe { msg_send![self.pasteboard, writeObjects:string_array] };
return if success {
Ok(())
} else {
Err(err("NSPasteboard#writeObjects: returned false"))
};
}
fn new() -> Result<OSXClipboardContext, Box<dyn Error>> {
let cls = Class::get("NSPasteboard").ok_or(err("Class::get(\"NSPasteboard\")"))?;
let pasteboard: *mut Object = unsafe { msg_send![cls, generalPasteboard] };
if pasteboard.is_null() {
return Err(err("NSPasteboard#generalPasteboard returned null"));
}
let pasteboard: Id<Object> = unsafe { Id::from_ptr(pasteboard) };
Ok(OSXClipboardContext { pasteboard })
}
fn get_text(&mut self) -> Result<String, Box<dyn Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(vec![string_class]);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let string_array: Id<NSArray<NSString>> = unsafe {
let obj: *mut NSArray<NSString> =
msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
if obj.is_null() {
return Err(err("pasteboard#readObjectsForClasses:options: returned null"));
}
Id::from_ptr(obj)
};
if string_array.count() == 0 {
Err(err("pasteboard#readObjectsForClasses:options: returned empty"))
} else {
Ok(string_array[0].as_str().to_owned())
}
}
fn set_text(&mut self, data: String) -> Result<(), Box<dyn Error>> {
let string_array = NSArray::from_vec(vec![NSString::from_str(&data)]);
let _: usize = unsafe { msg_send![self.pasteboard, clearContents] };
let success: bool = unsafe { msg_send![self.pasteboard, writeObjects: string_array] };
return if success {
Ok(())
} else {
Err(err("NSPasteboard#writeObjects: returned false"))
};
}
fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<dyn Error>> {
let string_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
unsafe { transmute(cls) }
};
let image_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSImage")) };
unsafe { transmute(cls) }
};
let url_class: Id<NSObject> = {
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSURL")) };
unsafe { transmute(cls) }
};
let classes = vec![url_class, image_class, string_class];
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(classes);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
let contents: Id<NSArray<NSObject>> = unsafe {
let obj: *mut NSArray<NSObject> =
msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
if obj.is_null() {
return Err(err("pasteboard#readObjectsForClasses:options: returned null"));
}
Id::from_ptr(obj)
};
if contents.count() == 0 {
Ok(None)
} else {
let obj = &contents[0];
if obj.is_kind_of(Class::get("NSString").unwrap()) {
let s: &NSString = unsafe { transmute(obj) };
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else if obj.is_kind_of(Class::get("NSImage").unwrap()) {
let tiff: &NSArray<NSObject> = unsafe { msg_send![obj, TIFFRepresentation] };
let len: usize = unsafe { msg_send![tiff, length] };
let bytes: *const u8 = unsafe { msg_send![tiff, bytes] };
let vec = unsafe { std::slice::from_raw_parts(bytes, len) };
// Here we copy the entire &[u8] into a new owned `Vec`
// Is there another way that doesn't copy multiple megabytes?
Ok(Some(ClipboardContent::Tiff(vec.into())))
} else if obj.is_kind_of(Class::get("NSURL").unwrap()) {
let s: &NSString = unsafe { msg_send![obj, absoluteString] };
Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
} else {
// let cls: &Class = unsafe { msg_send![obj, class] };
// println!("{}", cls.name());
Err(err("pasteboard#readObjectsForClasses:options: returned unknown class"))
}
}
}
fn get_image(&mut self) -> Result<ImageData, Box<dyn Error>> {
Err("Not implemented".into())
// let image_class: Id<NSObject> = {
// let cls: Id<Class> = unsafe { Id::from_ptr(class("NSImage")) };
// unsafe { transmute(cls) }
// };
// let classes = vec![image_class];
// let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(classes);
// let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
// let contents: Id<NSArray<NSObject>> = unsafe {
// let obj: *mut NSArray<NSObject> =
// msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
// if obj.is_null() {
// return Err(err(
// "pasteboard#readObjectsForClasses:options: returned null",
// ));
// }
// Id::from_ptr(obj)
// };
// if contents.count() == 0 {
// Err("No content on the clipboard".into())
// } else {
// let obj = &contents[0];
// if obj.is_kind_of(Class::get("NSString").unwrap()) {
// let s: &NSString = unsafe { transmute(obj) };
// Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
// } else if obj.is_kind_of(Class::get("NSImage").unwrap()) {
// let tiff: &NSArray<NSObject> = unsafe { msg_send![obj, TIFFRepresentation] };
// let len: usize = unsafe { msg_send![tiff, length] };
// let bytes: *const u8 = unsafe { msg_send![tiff, bytes] };
// let vec = unsafe { std::slice::from_raw_parts(bytes, len) };
// // Here we copy the entire &[u8] into a new owned `Vec`
// // Is there another way that doesn't copy multiple megabytes?
// Ok(Some(ClipboardContent::Tiff(vec.into())))
// } else if obj.is_kind_of(Class::get("NSURL").unwrap()) {
// let s: &NSString = unsafe { msg_send![obj, absoluteString] };
// Ok(Some(ClipboardContent::Utf8(s.as_str().to_owned())))
// } else {
// // let cls: &Class = unsafe { msg_send![obj, class] };
// // println!("{}", cls.name());
// Err(err(
// "pasteboard#readObjectsForClasses:options: returned unknown class",
// ))
// }
//}
}
fn set_image(&mut self, data: ImageData) -> Result<(), Box<dyn Error>> {
let pixels = data.bytes.into();
let image = image_from_pixels(pixels, data.width, data.height)?;
let objects: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(vec![image]);
let _: usize = unsafe { msg_send![self.pasteboard, clearContents] };
let success: BOOL = unsafe { msg_send![self.pasteboard, writeObjects: objects] };
if success == NO {
return Err(
"Failed to write the image to the pasteboard (`writeObjects` returned NO).".into(),
);
}
Ok(())
}
}
// this is a convenience function that both cocoa-rs and
@ -168,5 +243,5 @@ impl ClipboardProvider for OSXClipboardContext {
// Option::None has the same representation as a null pointer
#[inline]
pub fn class(name: &str) -> *mut Class {
unsafe { transmute(Class::get(name)) }
unsafe { transmute(Class::get(name)) }
}

View file

@ -15,14 +15,16 @@ limitations under the License.
*/
use byteorder::ByteOrder;
use clipboard_win::{get_clipboard_string, set_clipboard_string, formats::CF_DIB, Clipboard as SystemClipboard};
use image::{
bmp::{BMPEncoder, BmpDecoder},
ColorType, ImageDecoder,
use clipboard_win::{
formats::CF_DIB, get_clipboard_string, set_clipboard_string, Clipboard as SystemClipboard,
};
use common::{ClipboardContent, ClipboardProvider, ImageData};
use image::{
bmp::{BMPEncoder, BmpDecoder},
ColorType, ImageDecoder,
};
use common::{ClipboardProvider, ClipboardContent, ImageData};
use std::error::Error;
use std::borrow::Cow;
use std::error::Error;
const BITMAP_FILE_HEADER_SIZE: usize = 14;
const BITMAP_INFO_HEADER_SIZE: usize = 40;
@ -30,56 +32,55 @@ const BITMAP_INFO_HEADER_SIZE: usize = 40;
pub struct WindowsClipboardContext;
impl ClipboardProvider for WindowsClipboardContext {
fn new() -> Result<Self, Box<dyn Error>> {
Ok(WindowsClipboardContext)
}
fn get_text(&mut self) -> Result<String, Box<dyn Error>> {
Ok(get_clipboard_string()?)
}
fn set_text(&mut self, data: String) -> Result<(), Box<dyn Error>> {
Ok(set_clipboard_string(&data)?)
}
fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<dyn Error>> {
Err("get_binary_contents is not yet implemented for windows.".into())
}
fn get_image(&mut self) -> Result<ImageData, Box<dyn Error>> {
let clipboard = SystemClipboard::new()?;
let bitmap_data = clipboard.get_dib()?;
let mut header: [u8; BITMAP_FILE_HEADER_SIZE] = [0; 14];
header[0] = b'B';
header[1] = b'M';
byteorder::LittleEndian::write_u32(
&mut header[2..6],
(bitmap_data.size() + BITMAP_FILE_HEADER_SIZE) as u32,
);
byteorder::LittleEndian::write_u32(
&mut header[10..14],
(BITMAP_INFO_HEADER_SIZE + BITMAP_FILE_HEADER_SIZE) as u32,
);
fn new() -> Result<Self, Box<dyn Error>> {
Ok(WindowsClipboardContext)
}
fn get_text(&mut self) -> Result<String, Box<dyn Error>> {
Ok(get_clipboard_string()?)
}
fn set_text(&mut self, data: String) -> Result<(), Box<dyn Error>> {
Ok(set_clipboard_string(&data)?)
}
fn get_binary_contents(&mut self) -> Result<Option<ClipboardContent>, Box<dyn Error>> {
Err("get_binary_contents is not yet implemented for windows.".into())
}
fn get_image(&mut self) -> Result<ImageData, Box<dyn Error>> {
let clipboard = SystemClipboard::new()?;
let bitmap_data = clipboard.get_dib()?;
let mut header: [u8; BITMAP_FILE_HEADER_SIZE] = [0; 14];
header[0] = b'B';
header[1] = b'M';
byteorder::LittleEndian::write_u32(
&mut header[2..6],
(bitmap_data.size() + BITMAP_FILE_HEADER_SIZE) as u32,
);
byteorder::LittleEndian::write_u32(
&mut header[10..14],
(BITMAP_INFO_HEADER_SIZE + BITMAP_FILE_HEADER_SIZE) as u32,
);
// TODO make a struct holding the header and the rest of the bitmap and implements Seek and Read.
// TODO make a struct holding the header and the rest of the bitmap and implements Seek and Read.
let cursor = std::io::Cursor::new(bitmap_data.as_bytes());
let bmp_image = BmpDecoder::new(cursor)?;
let (w, h) = bmp_image.dimensions();
let width = w as usize;
let height = h as usize;
let buffer_size = width * height * 4;
let mut bytes = Vec::with_capacity(buffer_size);
bytes.resize(buffer_size, 0);
bmp_image.read_image(&mut bytes)?;
Ok(ImageData { width, height, bytes: Cow::from(bytes) })
}
fn set_image(&mut self, image: ImageData) -> Result<(), Box<dyn Error>> {
let clipboard = SystemClipboard::new()?;
let mut bmp_data = Vec::with_capacity(image.bytes.len());
let mut cursor = std::io::Cursor::new(&mut bmp_data);
let mut encoder = BMPEncoder::new(&mut cursor);
encoder
.encode(&image.bytes, image.width as u32, image.height as u32, ColorType::Rgba8)?;
let cursor = std::io::Cursor::new(bitmap_data.as_bytes());
let bmp_image = BmpDecoder::new(cursor)?;
let (w, h) = bmp_image.dimensions();
let width = w as usize;
let height = h as usize;
let buffer_size = width * height * 4;
let mut bytes = Vec::with_capacity(buffer_size);
bytes.resize(buffer_size, 0);
bmp_image.read_image(&mut bytes)?;
Ok(ImageData { width, height, bytes: Cow::from(bytes) })
}
fn set_image(&mut self, image: ImageData) -> Result<(), Box<dyn Error>> {
let clipboard = SystemClipboard::new()?;
let mut bmp_data = Vec::with_capacity(image.bytes.len());
let mut cursor = std::io::Cursor::new(&mut bmp_data);
let mut encoder = BMPEncoder::new(&mut cursor);
encoder.encode(&image.bytes, image.width as u32, image.height as u32, ColorType::Rgba8)?;
let data_without_file_header = &bmp_data[BITMAP_FILE_HEADER_SIZE..];
clipboard.set(CF_DIB, data_without_file_header)?;
Ok(())
}
let data_without_file_header = &bmp_data[BITMAP_FILE_HEADER_SIZE..];
clipboard.set(CF_DIB, data_without_file_header)?;
Ok(())
}
}

View file

@ -14,60 +14,56 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
use std::error::Error;
use std::time::Duration;
use std::marker::PhantomData;
use common::*;
use std::error::Error;
use std::marker::PhantomData;
use std::time::Duration;
use x11_clipboard_crate::xcb::xproto::Atom;
use x11_clipboard_crate::Atoms;
use x11_clipboard_crate::Clipboard as X11Clipboard;
use x11_clipboard_crate::xcb::xproto::Atom;
pub trait Selection {
fn atom(atoms: &Atoms) -> Atom;
fn atom(atoms: &Atoms) -> Atom;
}
pub struct Primary;
impl Selection for Primary {
fn atom(atoms: &Atoms) -> Atom {
atoms.primary
}
fn atom(atoms: &Atoms) -> Atom {
atoms.primary
}
}
pub struct Clipboard;
impl Selection for Clipboard {
fn atom(atoms: &Atoms) -> Atom {
atoms.clipboard
}
fn atom(atoms: &Atoms) -> Atom {
atoms.clipboard
}
}
pub struct X11ClipboardContext<S = Clipboard>(X11Clipboard, PhantomData<S>)
where
S: Selection;
S: Selection;
impl<S> ClipboardProvider for X11ClipboardContext<S>
where
S: Selection,
S: Selection,
{
fn new() -> Result<X11ClipboardContext<S>, Box<Error>> {
Ok(X11ClipboardContext(X11Clipboard::new()?, PhantomData))
}
fn new() -> Result<X11ClipboardContext<S>, Box<Error>> {
Ok(X11ClipboardContext(X11Clipboard::new()?, PhantomData))
}
fn get_text(&mut self) -> Result<String, Box<Error>> {
Ok(String::from_utf8(self.0.load(
S::atom(&self.0.getter.atoms),
self.0.getter.atoms.utf8_string,
self.0.getter.atoms.property,
Duration::from_secs(3),
)?)?)
}
fn get_text(&mut self) -> Result<String, Box<Error>> {
Ok(String::from_utf8(self.0.load(
S::atom(&self.0.getter.atoms),
self.0.getter.atoms.utf8_string,
self.0.getter.atoms.property,
Duration::from_secs(3),
)?)?)
}
fn set_text(&mut self, data: String) -> Result<(), Box<Error>> {
Ok(self.0.store(
S::atom(&self.0.setter.atoms),
self.0.setter.atoms.utf8_string,
data,
)?)
}
fn set_text(&mut self, data: String) -> Result<(), Box<Error>> {
Ok(self.0.store(S::atom(&self.0.setter.atoms), self.0.setter.atoms.utf8_string, data)?)
}
}