mirror of
https://github.com/1Password/arboard.git
synced 2025-07-07 13:25:18 +00:00
Implemented set_image
on macOS.
This commit is contained in:
parent
1b363cd7a2
commit
e4545b2231
11 changed files with 1122 additions and 276 deletions
|
@ -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]
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
767
osx_clipboard_expanded.rs
Normal 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
4
rustfmt.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
hard_tabs=true
|
||||
use_field_init_shorthand=true
|
||||
use_small_heuristics="Max"
|
||||
use_try_shorthand=true
|
|
@ -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>>;
|
||||
}
|
||||
|
|
32
src/lib.rs
32
src/lib.rs
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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)) }
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue