Skip to content

Commit fcdbf53

Browse files
authored
Merge pull request #27 from madsmtm/objc2
Use `objc2` and its framework crates
2 parents 4bd0f74 + d4994e5 commit fcdbf53

File tree

2 files changed

+56
-58
lines changed

2 files changed

+56
-58
lines changed

macos/Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ keywords = ["clipboard", "macos"]
1313
default-target = "x86_64-apple-darwin"
1414

1515
[dependencies]
16-
objc = "0.2"
17-
objc_id = "0.1"
18-
objc-foundation = "0.1"
16+
objc2 = "0.5.1"
17+
objc2-foundation = { version = "0.2.0", features = [
18+
"NSArray",
19+
"NSString",
20+
"NSURL",
21+
] }
22+
objc2-app-kit = { version = "0.2.0", features = ["NSPasteboard"] }

macos/src/lib.rs

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,80 +11,74 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
#[macro_use]
15-
extern crate objc;
1614

17-
use objc::runtime::{Class, Object};
18-
use objc_foundation::{INSArray, INSObject, INSString};
19-
use objc_foundation::{NSArray, NSDictionary, NSObject, NSString};
20-
use objc_id::{Id, Owned};
15+
use objc2::rc::Id;
16+
use objc2::runtime::{AnyClass, AnyObject, ProtocolObject};
17+
use objc2::{msg_send_id, ClassType};
18+
use objc2_app_kit::NSPasteboard;
19+
use objc2_foundation::{NSArray, NSString};
2120
use std::error::Error;
22-
use std::mem::transmute;
21+
use std::panic::{RefUnwindSafe, UnwindSafe};
2322

2423
pub struct Clipboard {
25-
pasteboard: Id<Object>,
24+
pasteboard: Id<NSPasteboard>,
2625
}
2726

28-
// required to bring NSPasteboard into the path of the class-resolver
29-
#[link(name = "AppKit", kind = "framework")]
30-
extern "C" {}
27+
unsafe impl Send for Clipboard {}
28+
unsafe impl Sync for Clipboard {}
29+
impl UnwindSafe for Clipboard {}
30+
impl RefUnwindSafe for Clipboard {}
3131

3232
impl Clipboard {
3333
pub fn new() -> Result<Clipboard, Box<dyn Error>> {
34-
let cls =
35-
Class::get("NSPasteboard").ok_or("Class::get(\"NSPasteboard\")")?;
36-
let pasteboard: *mut Object =
37-
unsafe { msg_send![cls, generalPasteboard] };
38-
if pasteboard.is_null() {
39-
return Err("NSPasteboard#generalPasteboard returned null".into());
40-
}
41-
let pasteboard: Id<Object> = unsafe { Id::from_ptr(pasteboard) };
42-
Ok(Clipboard { pasteboard })
34+
// Use `msg_send_id!` instead of `NSPasteboard::generalPasteboard()`
35+
// in the off case that it will return NULL (even though it's
36+
// documented not to).
37+
let pasteboard: Option<Id<NSPasteboard>> =
38+
unsafe { msg_send_id![NSPasteboard::class(), generalPasteboard] };
39+
let pasteboard =
40+
pasteboard.ok_or("NSPasteboard#generalPasteboard returned null")?;
41+
Ok(Self { pasteboard })
4342
}
4443

4544
pub fn read(&self) -> Result<String, Box<dyn Error>> {
46-
let string_class: Id<NSObject> = {
47-
let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
48-
unsafe { transmute(cls) }
49-
};
50-
let classes: Id<NSArray<NSObject, Owned>> =
51-
NSArray::from_vec(vec![string_class]);
52-
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
53-
let string_array: Id<NSArray<NSString>> = unsafe {
54-
let obj: *mut NSArray<NSString> = msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
55-
if obj.is_null() {
56-
return Err(
57-
"pasteboard#readObjectsForClasses:options: returned null"
58-
.into(),
59-
);
60-
}
61-
Id::from_ptr(obj)
45+
// The NSPasteboard API is a bit weird, it requires you to pass
46+
// classes as objects, which `objc2_foundation::NSArray` was not really
47+
// made for - so we convert the class to an `AnyObject` type instead.
48+
//
49+
// TODO: Use the NSPasteboard helper APIs (`stringForType`).
50+
let string_class = {
51+
let cls: *const AnyClass = NSString::class();
52+
let cls = cls as *mut AnyObject;
53+
unsafe { Id::retain(cls).unwrap() }
6254
};
63-
if string_array.count() == 0 {
64-
Err("pasteboard#readObjectsForClasses:options: returned empty"
65-
.into())
66-
} else {
67-
Ok(string_array[0].as_str().to_owned())
55+
let classes = NSArray::from_vec(vec![string_class]);
56+
let string_array = unsafe {
57+
self.pasteboard
58+
.readObjectsForClasses_options(&classes, None)
6859
}
60+
.ok_or("pasteboard#readObjectsForClasses:options: returned null")?;
61+
62+
let obj: *const AnyObject = string_array.first().ok_or(
63+
"pasteboard#readObjectsForClasses:options: returned empty",
64+
)?;
65+
// And this part is weird as well, since we now have to convert the object
66+
// into an NSString, which we know it to be since that's what we told
67+
// `readObjectsForClasses:options:`.
68+
let obj: *mut NSString = obj as _;
69+
Ok(unsafe { Id::retain(obj) }.unwrap().to_string())
6970
}
7071

7172
pub fn write(&mut self, data: String) -> Result<(), Box<dyn Error>> {
72-
let string_array = NSArray::from_vec(vec![NSString::from_str(&data)]);
73-
let _: usize = unsafe { msg_send![self.pasteboard, clearContents] };
74-
let success: bool =
75-
unsafe { msg_send![self.pasteboard, writeObjects: string_array] };
76-
return if success {
73+
let string_array = NSArray::from_vec(vec![ProtocolObject::from_id(
74+
NSString::from_str(&data),
75+
)]);
76+
unsafe { self.pasteboard.clearContents() };
77+
let success = unsafe { self.pasteboard.writeObjects(&string_array) };
78+
if success {
7779
Ok(())
7880
} else {
7981
Err("NSPasteboard#writeObjects: returned false".into())
80-
};
82+
}
8183
}
8284
}
83-
84-
// this is a convenience function that both cocoa-rs and
85-
// glutin define, which seems to depend on the fact that
86-
// Option::None has the same representation as a null pointer
87-
#[inline]
88-
pub fn class(name: &str) -> *mut Class {
89-
unsafe { transmute(Class::get(name)) }
90-
}

0 commit comments

Comments
 (0)