Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix MacOS memory leaks. #97

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target
.vscode
*.png
.idea/
124 changes: 65 additions & 59 deletions src/platform/osx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ use core_graphics::{
};
use objc::{
msg_send,
runtime::{Class, Object},
rc::autoreleasepool,
runtime::{Class, Object, BOOL, NO},
sel, sel_impl,
};
use objc_foundation::{INSArray, INSObject, INSString, NSArray, NSDictionary, NSObject, NSString};
use objc_id::{Id, Owned};
use objc_foundation::{INSArray, INSString, NSArray, NSString};
#[cfg(feature = "image-data")]
use objc_foundation::{INSObject, NSDictionary, NSObject};
use objc_id::Id;
#[cfg(feature = "image-data")]
use objc_id::Owned;
use once_cell::sync::Lazy;
use std::borrow::Cow;

Expand All @@ -35,6 +40,7 @@ extern "C" {
static NSPasteboardTypeString: *const Object;
}

#[allow(unused)]
static NSSTRING_CLASS: Lazy<&Class> = Lazy::new(|| Class::get("NSString").unwrap());
#[cfg(feature = "image-data")]
static NSIMAGE_CLASS: Lazy<&Class> = Lazy::new(|| Class::get("NSImage").unwrap());
Expand Down Expand Up @@ -179,73 +185,72 @@ impl<'clipboard> Get<'clipboard> {
}

pub(crate) fn text(self) -> Result<String, Error> {
let string_class = object_class(&NSSTRING_CLASS);
let classes: Id<NSArray<NSObject, Owned>> = NSArray::from_vec(vec![string_class]);
let options: Id<NSDictionary<NSObject, NSObject>> = NSDictionary::new();
autoreleasepool(|| unsafe {
let types: *mut NSArray<*mut NSString> = msg_send![self.pasteboard, types];
let has_str: BOOL = msg_send![types, containsObject: NSPasteboardTypeString];

if has_str == NO {
return Err(Error::ContentNotAvailable);
}

let string_array: Id<NSArray<NSString>> = unsafe {
let obj: *mut NSArray<NSString> =
msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];
let text: *mut NSString =
msg_send![self.pasteboard, stringForType: NSPasteboardTypeString];

if obj.is_null() {
if text.is_null() {
return Err(Error::ContentNotAvailable);
} else {
Id::from_ptr(obj)
}
};

string_array
.first_object()
.map(|obj| obj.as_str().to_owned())
.ok_or(Error::ContentNotAvailable)
Ok((*text).as_str().to_owned())
})
}

#[cfg(feature = "image-data")]
pub(crate) fn image(self) -> Result<ImageData<'static>, Error> {
use std::io::Cursor;

let image_class: Id<NSObject> = object_class(&NSIMAGE_CLASS);
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(Error::ContentNotAvailable);
} else {
Id::from_ptr(obj)
autoreleasepool(|| unsafe {
let image_class: Id<NSObject> = object_class(&NSIMAGE_CLASS);
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>> = {
let obj: *mut NSArray<NSObject> =
msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options];

if obj.is_null() {
return Err(Error::ContentNotAvailable);
} else {
Id::from_ptr(obj)
}
};

let obj = match contents.first_object() {
Some(obj) if obj.is_kind_of(&NSIMAGE_CLASS) => obj,
Some(_) | None => return Err(Error::ContentNotAvailable),
};

let data = {
let tiff: &NSArray<NSObject> = msg_send![obj, TIFFRepresentation];
let len: usize = msg_send![tiff, length];
let bytes: *const u8 = msg_send![tiff, bytes];
Cursor::new(std::slice::from_raw_parts(bytes, len))
};

let reader = image::io::Reader::with_format(data, image::ImageFormat::Tiff);
match reader.decode() {
Ok(img) => {
let rgba = img.into_rgba8();
let (width, height) = rgba.dimensions();

Ok(ImageData {
width: width as usize,
height: height as usize,
bytes: rgba.into_raw().into(),
})
}
Err(_) => Err(Error::ConversionFailure),
}
};

let obj = match contents.first_object() {
Some(obj) if obj.is_kind_of(&NSIMAGE_CLASS) => obj,
Some(_) | None => return Err(Error::ContentNotAvailable),
};

let tiff: &NSArray<NSObject> = unsafe { msg_send![obj, TIFFRepresentation] };
let data = unsafe {
let len: usize = msg_send![tiff, length];
let bytes: *const u8 = msg_send![tiff, bytes];

Cursor::new(std::slice::from_raw_parts(bytes, len))
};
let reader = image::io::Reader::with_format(data, image::ImageFormat::Tiff);
match reader.decode() {
Ok(img) => {
let rgba = img.into_rgba8();
let (width, height) = rgba.dimensions();

Ok(ImageData {
width: width as usize,
height: height as usize,
bytes: rgba.into_raw().into(),
})
}
Err(_) => Err(Error::ConversionFailure),
}
})
}
}

Expand Down Expand Up @@ -341,6 +346,7 @@ impl<'clipboard> Clear<'clipboard> {

/// Convenience function to get an Objective-C object from a
/// specific class.
#[cfg(feature = "image-data")]
fn object_class(class: &'static Class) -> Id<NSObject> {
// SAFETY: `Class` is a valid object and `Id` will not mutate it
unsafe { Id::from_ptr(class as *const Class as *mut NSObject) }
Expand Down