Skip to content

Commit

Permalink
Use objc2 and its framework crates
Browse files Browse the repository at this point in the history
This catches a few extra error cases related to paths, as well as making
it easier to ensure that memory management rules are upheld.

Concretely, it fixes a leak in the passing of the `NSArray` to
`writeObjects`.
  • Loading branch information
madsmtm committed Apr 30, 2024
1 parent a7b0b21 commit 0c2ace5
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 45 deletions.
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ wayland = ["smithay-clipboard"]
clipboard-win = "3.0.2"

[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
objc_id = "0.1"
objc-foundation = "0.1"
objc2 = "0.5.1"
objc2-foundation = { version = "0.2.0", features = [
"NSArray",
"NSString",
"NSURL",
] }
objc2-app-kit = { version = "0.2.0", features = ["NSPasteboard"] }

[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="ios", target_os="emscripten"))))'.dependencies]
x11-clipboard = { version = "0.9.1", optional = true }
Expand Down
81 changes: 39 additions & 42 deletions src/osx_clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,74 +12,71 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use objc::rc::autoreleasepool;
use objc::runtime::{Class, Object, BOOL, NO, YES};
use objc::{class, msg_send, sel, sel_impl};
use objc_foundation::{INSArray, INSString};
use objc_foundation::{NSArray, NSString};
use objc_id::Id;
use std::panic::RefUnwindSafe;
use std::panic::UnwindSafe;

use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::ProtocolObject;
use objc2::{msg_send_id, ClassType};
use objc2_app_kit::{NSPasteboard, NSPasteboardTypeFileURL, NSPasteboardTypeString};
use objc2_foundation::{NSArray, NSString, NSURL};

use crate::common::*;

pub struct OSXClipboardContext {
pasteboard: Id<Object>,
pasteboard: Id<NSPasteboard>,
}

// required to bring NSPasteboard into the path of the class-resolver
#[link(name = "AppKit", kind = "framework")]
extern "C" {
pub static NSPasteboardTypeFileURL: *mut Object;
pub static NSPasteboardTypeString: *mut Object;
}
unsafe impl Send for OSXClipboardContext {}
unsafe impl Sync for OSXClipboardContext {}
impl UnwindSafe for OSXClipboardContext {}
impl RefUnwindSafe for OSXClipboardContext {}

impl OSXClipboardContext {
pub fn new() -> Result<OSXClipboardContext> {
let cls = Class::get("NSPasteboard").ok_or("Class::get(\"NSPasteboard\")")?;
let pasteboard: *mut Object = unsafe { msg_send![cls, generalPasteboard] };
if pasteboard.is_null() {
return Err("NSPasteboard#generalPasteboard returned null".into());
}
let pasteboard: Id<Object> = unsafe { Id::from_ptr(pasteboard) };
// Use `msg_send_id!` instead of `NSPasteboard::generalPasteboard()`
// in the off case that it will return NULL (even though it's
// documented not to).
let pasteboard: Option<Id<NSPasteboard>> =
unsafe { msg_send_id![NSPasteboard::class(), generalPasteboard] };
let pasteboard = pasteboard.ok_or("NSPasteboard#generalPasteboard returned null")?;
Ok(OSXClipboardContext { pasteboard })
}
}

impl ClipboardProvider for OSXClipboardContext {
fn get_contents(&mut self) -> Result<String> {
autoreleasepool(|| unsafe {
let types: *mut NSArray<*mut NSString> = msg_send![self.pasteboard, types];
let has_file: BOOL = msg_send![types, containsObject: NSPasteboardTypeFileURL];
let has_str: BOOL = msg_send![types, containsObject: NSPasteboardTypeString];
autoreleasepool(|_| {
let types = unsafe { self.pasteboard.types() }.unwrap();
let has_file = unsafe { types.containsObject(NSPasteboardTypeFileURL) };
let has_str = unsafe { types.containsObject(NSPasteboardTypeString) };

if has_str == NO {
if !has_str {
return Err("NSPasteboard#types doesn't contain NSPasteboardTypeString".into());
}

let text = if has_file == YES {
let file_url_string: *mut NSString =
msg_send![self.pasteboard, stringForType: NSPasteboardTypeFileURL];
let file_url: *mut Object =
msg_send![class!(NSURL), URLWithString: file_url_string];
let text: *mut NSString = msg_send![file_url, path];
text
let text = if has_file {
let file_url_string =
unsafe { self.pasteboard.stringForType(NSPasteboardTypeFileURL) }
.ok_or("NSPasteboard#stringForType returned null")?;

let file_url = unsafe { NSURL::URLWithString(&file_url_string) }
.ok_or("NSURL#URLWithString returned null")?;
unsafe { file_url.path() }.ok_or("NSURL#path returned null")?
} else {
let text: *mut NSString =
msg_send![self.pasteboard, stringForType: NSPasteboardTypeString];
text
unsafe { self.pasteboard.stringForType(NSPasteboardTypeString) }
.ok_or("NSPasteboard#stringForType returned null")?
};

if text.is_null() {
return Err(("NSPasteboard#stringForType returned null").into());
}

Ok((*text).as_str().to_owned())
Ok(text.to_string())
})
}

fn set_contents(&mut self, data: String) -> Result<()> {
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] };
let string_array =
NSArray::from_vec(vec![ProtocolObject::from_id(NSString::from_str(&data))]);
unsafe { self.pasteboard.clearContents() };
let success = unsafe { self.pasteboard.writeObjects(&string_array) };
if success {
Ok(())
} else {
Expand Down

0 comments on commit 0c2ace5

Please sign in to comment.