From c16dad564e84fc319fea4625b6690583e95c5154 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Tue, 17 Mar 2020 16:55:09 -0700 Subject: [PATCH] Wrap Foundation ourselves, because there's some considerations down the road and it's easier to do this now. --- appkit/Cargo.toml | 8 +- appkit/build.rs | 3 + appkit/src/alert.rs | 37 +++---- appkit/src/app/class.rs | 46 ++++---- appkit/src/app/enums.rs | 31 +----- appkit/src/app/mod.rs | 44 ++++---- appkit/src/app/traits.rs | 17 ++- appkit/src/bundle.rs | 4 +- appkit/src/button.rs | 14 ++- appkit/src/color.rs | 4 +- appkit/src/dragdrop.rs | 3 +- appkit/src/error.rs | 14 ++- appkit/src/events.rs | 2 +- appkit/src/foundation/array.rs | 82 +++++++++++++++ appkit/src/foundation/autoreleasepool.rs | 19 ++++ appkit/src/foundation/dictionary.rs | 10 ++ appkit/src/foundation/geometry.rs | 81 ++++++++++++++ appkit/src/foundation/mod.rs | 51 +++++++++ appkit/src/foundation/string.rs | 56 ++++++++++ appkit/src/geometry.rs | 12 +-- appkit/src/layout/constraint.rs | 5 +- appkit/src/layout/dimension.rs | 4 +- appkit/src/layout/horizontal.rs | 3 +- appkit/src/layout/vertical.rs | 3 +- appkit/src/lib.rs | 13 +-- appkit/src/menu/item.rs | 10 +- appkit/src/menu/menu.rs | 6 +- appkit/src/networking/mod.rs | 13 +-- appkit/src/pasteboard/mod.rs | 96 ++++++++++++++++- appkit/src/pasteboard/pasteboard.rs | 128 ----------------------- appkit/src/pasteboard/types.rs | 65 +++++------- appkit/src/printing/enums.rs | 32 ++++++ appkit/src/printing/mod.rs | 3 + appkit/src/printing/settings.rs | 3 +- appkit/src/toolbar/class.rs | 47 ++++----- appkit/src/toolbar/handle.rs | 4 +- appkit/src/toolbar/item.rs | 12 +-- appkit/src/toolbar/toolbar.rs | 6 +- appkit/src/toolbar/types.rs | 2 +- appkit/src/user_activity.rs | 3 +- appkit/src/utils.rs | 42 -------- appkit/src/webview/process_pool.rs | 2 +- appkit/src/webview/webview.rs | 4 +- 43 files changed, 613 insertions(+), 431 deletions(-) create mode 100644 appkit/src/foundation/array.rs create mode 100644 appkit/src/foundation/autoreleasepool.rs create mode 100644 appkit/src/foundation/dictionary.rs create mode 100644 appkit/src/foundation/geometry.rs create mode 100644 appkit/src/foundation/mod.rs create mode 100644 appkit/src/foundation/string.rs delete mode 100644 appkit/src/pasteboard/pasteboard.rs create mode 100644 appkit/src/printing/enums.rs diff --git a/appkit/Cargo.toml b/appkit/Cargo.toml index 1be29b4..17e0f0c 100644 --- a/appkit/Cargo.toml +++ b/appkit/Cargo.toml @@ -6,16 +6,12 @@ edition = "2018" build = "build.rs" [dependencies] -appkit-derive = { path = "../derives" } block = "0.1.6" -cocoa = "0.20.0" -core-foundation = "0.7" -core-graphics = "0.19.0" dispatch = "0.2.0" -lazy_static = "1" +libc = "0.2" objc = "0.2.7" objc_id = "0.1.1" -uuid = { version = "0.8", features = ["v4"] } +#uuid = { version = "0.8", features = ["v4"] } url = "2.1.1" [features] diff --git a/appkit/build.rs b/appkit/build.rs index 3d85dcb..411e4f7 100644 --- a/appkit/build.rs +++ b/appkit/build.rs @@ -5,6 +5,9 @@ fn main() { if std::env::var("TARGET").unwrap().contains("-apple") { + println!("cargo:rustc-link-lib=framework=Foundation"); + println!("cargo:rustc-link-lib=framework=CoreGraphics"); + println!("cargo:rustc-link-lib=framework=Security"); println!("cargo:rustc-link-lib=framework=WebKit"); diff --git a/appkit/src/alert.rs b/appkit/src/alert.rs index afbf392..825562f 100644 --- a/appkit/src/alert.rs +++ b/appkit/src/alert.rs @@ -1,46 +1,37 @@ //! A wrapper for `NSAlert`. Currently doesn't cover everything possible for this class, as it was //! built primarily for debugging uses. Feel free to extend via pull requests or something. -use cocoa::base::{id, nil}; -use cocoa::foundation::NSString; - use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, NSString}; + /// Represents an `NSAlert`. Has no information other than the retained pointer to the Objective C /// side, so... don't bother inspecting this. -pub struct Alert { - pub inner: Id -} +pub struct Alert(Id); impl Alert { /// Creates a basic `NSAlert`, storing a pointer to it in the Objective C runtime. /// You can show this alert by calling `show()`. pub fn new(title: &str, message: &str) -> Self { - Alert { - inner: unsafe { - let cls = class!(NSAlert); - let alert: id = msg_send![cls, new]; + let title = NSString::new(title); + let message = NSString::new(message); + let x = NSString::new("OK"); - let title = NSString::alloc(nil).init_str(title); - let _: () = msg_send![alert, setMessageText:title]; - - let message = NSString::alloc(nil).init_str(message); - let _: () = msg_send![alert, setInformativeText:message]; - - let x = NSString::alloc(nil).init_str("OK"); - let _: () = msg_send![alert, addButtonWithTitle:x]; - - Id::from_ptr(alert) - } - } + Alert(unsafe { + let alert: id = msg_send![class!(NSAlert), new]; + let _: () = msg_send![alert, setMessageText:title]; + let _: () = msg_send![alert, setInformativeText:message]; + let _: () = msg_send![alert, addButtonWithTitle:x]; + Id::from_ptr(alert) + }) } /// Shows this alert as a modal. pub fn show(&self) { unsafe { - let _: () = msg_send![&*self.inner, runModal]; + let _: () = msg_send![&*self.0, runModal]; } } } diff --git a/appkit/src/app/class.rs b/appkit/src/app/class.rs index f073d65..ccd7baf 100644 --- a/appkit/src/app/class.rs +++ b/appkit/src/app/class.rs @@ -8,21 +8,18 @@ use std::unreachable; use block::Block; -use cocoa::base::{id, nil, BOOL, YES, NO}; -use cocoa::foundation::{NSUInteger}; - use objc::{class, msg_send, sel, sel_impl}; use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use url::Url; +use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString}; use crate::app::traits::AppController; use crate::constants::APP_PTR; use crate::error::AppKitError; use crate::printing::PrintSettings; use crate::user_activity::UserActivity; -use crate::utils::{map_nsarray, str_from}; #[cfg(feature = "cloudkit")] use crate::cloudkit::share::CKShareMetaData; @@ -122,7 +119,6 @@ extern fn should_handle_reopen(this: &Object, _: Sel, _: id, h /// Fires when the application delegate receives a `applicationDockMenu:` request. extern fn dock_menu(this: &Object, _: Sel, _: id) -> id { - // @TODO: Confirm this is safe to do and not leaky. match app::(this).dock_menu() { Some(mut menu) => &mut *menu.inner, None => nil @@ -143,9 +139,9 @@ extern fn did_change_screen_parameters(this: &Object, _: Sel, /// Fires when the application receives a `application:willContinueUserActivityWithType:` /// notification. extern fn will_continue_user_activity_with_type(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL { - let activity = str_from(activity_type); + let activity = NSString::wrap(activity_type); - match app::(this).will_continue_user_activity(activity) { + match app::(this).will_continue_user_activity(activity.to_str()) { true => YES, false => NO } @@ -171,7 +167,7 @@ extern fn continue_user_activity(this: &Object, _: Sel, _: id, /// `application:didFailToContinueUserActivityWithType:error:` message. extern fn failed_to_continue_user_activity(this: &Object, _: Sel, _: id, activity_type: id, error: id) { app::(this).failed_to_continue_user_activity( - str_from(activity_type), + NSString::wrap(activity_type).to_str(), AppKitError::new(error) ); } @@ -188,8 +184,8 @@ extern fn registered_for_remote_notifications(_this: &Object, } /// Fires when the application receives a `application:didFailToRegisterForRemoteNotificationsWithError:` message. -extern fn failed_to_register_for_remote_notifications(_this: &Object, _: Sel, _: id, _: id) { - +extern fn failed_to_register_for_remote_notifications(this: &Object, _: Sel, _: id, error: id) { + app::(this).failed_to_register_for_remote_notifications(AppKitError::new(error)); } /// Fires when the application receives a `application:didReceiveRemoteNotification:` message. @@ -207,10 +203,12 @@ extern fn accepted_cloudkit_share(_this: &Object, _: Sel, _: i /// Fires when the application receives an `application:openURLs` message. extern fn open_urls(this: &Object, _: Sel, _: id, file_urls: id) { - let urls = map_nsarray(file_urls, |url| unsafe { - let absolute_string = msg_send![url, absoluteString]; - let uri = str_from(absolute_string); - Url::parse(uri) + let urls = NSArray::wrap(file_urls).map(|url| { + let uri = NSString::wrap(unsafe { + msg_send![url, absoluteString] + }); + + Url::parse(uri.to_str()) }).into_iter().filter_map(|url| url.ok()).collect(); app::(this).open_urls(urls); @@ -218,9 +216,9 @@ extern fn open_urls(this: &Object, _: Sel, _: id, file_urls: i /// Fires when the application receives an `application:openFileWithoutUI:` message. extern fn open_file_without_ui(this: &Object, _: Sel, _: id, file: id) -> BOOL { - let filename = str_from(file); + let filename = NSString::wrap(file); - match app::(this).open_file_without_ui(filename) { + match app::(this).open_file_without_ui(filename.to_str()) { true => YES, false => NO } @@ -244,9 +242,9 @@ extern fn open_untitled_file(this: &Object, _: Sel, _: id) -> /// Fired when the application receives an `application:openTempFile:` message. extern fn open_temp_file(this: &Object, _: Sel, _: id, filename: id) -> BOOL { - let filename = str_from(filename); + let filename = NSString::wrap(filename); - match app::(this).open_temp_file(filename) { + match app::(this).open_temp_file(filename.to_str()) { true => YES, false => NO } @@ -254,9 +252,9 @@ extern fn open_temp_file(this: &Object, _: Sel, _: id, filenam /// Fired when the application receives an `application:printFile:` message. extern fn print_file(this: &Object, _: Sel, _: id, file: id) -> BOOL { - let filename = str_from(file); + let filename = NSString::wrap(file); - match app::(this).print_file(filename) { + match app::(this).print_file(filename.to_str()) { true => YES, false => NO } @@ -265,8 +263,8 @@ extern fn print_file(this: &Object, _: Sel, _: id, file: id) - /// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:` /// message. extern fn print_files(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger { - let files = map_nsarray(files, |file| { - str_from(file).to_string() + let files = NSArray::wrap(files).map(|file| { + NSString::wrap(file).to_str().to_string() }); let settings = PrintSettings::with_inner(settings); @@ -287,9 +285,9 @@ extern fn did_change_occlusion_state(this: &Object, _: Sel, _: /// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the /// matter. extern fn delegate_handles_key(this: &Object, _: Sel, _: id, key: id) -> BOOL { - let key = str_from(key); + let key = NSString::wrap(key); - match app::(this).delegate_handles_key(key) { + match app::(this).delegate_handles_key(key.to_str()) { true => YES, false => NO } diff --git a/appkit/src/app/enums.rs b/appkit/src/app/enums.rs index ccdd23f..327cdf5 100644 --- a/appkit/src/app/enums.rs +++ b/appkit/src/app/enums.rs @@ -1,6 +1,6 @@ //! Various types used at the AppController level. -use cocoa::foundation::NSUInteger; +use crate::foundation::NSUInteger; /// Used for determining how an application should handle quitting/terminating. /// You return this in your `AppController` `should_terminate` method. @@ -29,32 +29,3 @@ impl From for NSUInteger { } } } - -/// Used for handling printing files. You return this in relevant `AppController` methods. -#[derive(Copy, Clone, Debug)] -pub enum PrintResponse { - /// Printing was cancelled. - Cancelled, - - /// Printing was a success. - Success, - - /// Printing failed. - Failure, - - /// For when the result of printing cannot be returned immediately (e.g, if printing causes a sheet to appear). - /// If your method returns PrintResponse::ReplyLater it must always invoke `App::reply_to_open_or_print()` when the - /// entire print operation has been completed, successfully or not. - ReplyLater -} - -impl From for NSUInteger { - fn from(response: PrintResponse) -> NSUInteger { - match response { - PrintResponse::Cancelled => 0, - PrintResponse::Success => 1, - PrintResponse::Failure => 3, - PrintResponse::ReplyLater => 2 - } - } -} diff --git a/appkit/src/app/mod.rs b/appkit/src/app/mod.rs index 6cab2ec..254e9c7 100644 --- a/appkit/src/app/mod.rs +++ b/appkit/src/app/mod.rs @@ -1,9 +1,6 @@ //! A wrapper for `NSApplicationDelegate` on macOS. Handles looping back events and providing a very janky //! messaging architecture. -use cocoa::base::{id, nil}; -use cocoa::appkit::{NSRunningApplication}; - use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; @@ -12,6 +9,7 @@ mod class; pub mod traits; pub mod enums; +use crate::foundation::{id, AutoReleasePool}; use crate::constants::APP_PTR; use crate::menu::Menu; use class::{register_app_class, register_app_controller_class}; @@ -24,6 +22,7 @@ pub struct App { pub inner: Id, pub objc_delegate: Id, pub delegate: Box, + pub pool: AutoReleasePool, _t: std::marker::PhantomData } @@ -51,35 +50,17 @@ impl App { } impl App where M: Send + Sync + 'static, T: AppController + Dispatcher { - /// Dispatches a message by grabbing the `sharedApplication`, getting ahold of the delegate, - /// and passing back through there. All messages are currently dispatched on the main thread. - pub fn dispatch(message: M) { - let queue = dispatch::Queue::main(); - - queue.exec_async(move || unsafe { - let app: id = msg_send![register_app_class(), sharedApplication]; - let app_delegate: id = msg_send![app, delegate]; - let delegate_ptr: usize = *(*app_delegate).get_ivar(APP_PTR); - let delegate = delegate_ptr as *const T; - (&*delegate).on_message(message); - }); - } - /// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation /// policies), injects an `NSObject` delegate wrapper, and retains everything on the /// Objective-C side of things. pub fn new(_bundle_id: &str, delegate: T) -> Self { // set_bundle_id(bundle_id); - let _pool = unsafe { - //msg_send![class!( - cocoa::foundation::NSAutoreleasePool::new(nil) - }; + let pool = AutoReleasePool::new(); let inner = unsafe { let app: id = msg_send![register_app_class(), sharedApplication]; let _: () = msg_send![app, setActivationPolicy:0]; - //app.setActivationPolicy_(cocoa::appkit::NSApplicationActivationPolicyRegular); Id::from_ptr(app) }; @@ -98,17 +79,32 @@ impl App where M: Send + Sync + 'static, T: AppController + Dispatch objc_delegate: objc_delegate, inner: inner, delegate: app_delegate, + pool: pool, _t: std::marker::PhantomData } } + + /// Dispatches a message by grabbing the `sharedApplication`, getting ahold of the delegate, + /// and passing back through there. All messages are currently dispatched on the main thread. + pub fn dispatch(message: M) { + let queue = dispatch::Queue::main(); + + queue.exec_async(move || unsafe { + let app: id = msg_send![register_app_class(), sharedApplication]; + let app_delegate: id = msg_send![app, delegate]; + let delegate_ptr: usize = *(*app_delegate).get_ivar(APP_PTR); + let delegate = delegate_ptr as *const T; + (&*delegate).on_message(message); + }); + } /// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called. /// If you're wondering where to go from here... you need an `AppController` that implements /// `did_finish_launching`. :) pub fn run(&self) { unsafe { - let current_app = cocoa::appkit::NSRunningApplication::currentApplication(nil); - current_app.activateWithOptions_(cocoa::appkit::NSApplicationActivateIgnoringOtherApps); + let current_app: id = msg_send![class!(NSRunningApplication), currentApplication]; + let _: () = msg_send![current_app, activateWithOptions:0]; let shared_app: id = msg_send![class!(RSTApplication), sharedApplication]; let _: () = msg_send![shared_app, run]; } diff --git a/appkit/src/app/traits.rs b/appkit/src/app/traits.rs index 87a3ddb..bb450e9 100644 --- a/appkit/src/app/traits.rs +++ b/appkit/src/app/traits.rs @@ -3,13 +3,14 @@ use url::Url; -use crate::app::enums::{TerminateResponse, PrintResponse}; +use crate::app::enums::TerminateResponse; use crate::error::AppKitError; use crate::menu::Menu; +use crate::printing::enums::PrintResponse; use crate::printing::settings::PrintSettings; use crate::user_activity::UserActivity; -#[cfg(feature = "user-notifications")] +#[cfg(feature = "cloudkit")] use crate::cloudkit::share::CKShareMetaData; /// Controllers interested in processing messages can implement this to respond to messages as @@ -121,6 +122,18 @@ pub trait AppController { /// Fired after the user activity object has been updated. fn updated_user_activity(&self, _activity: UserActivity) {} + /// Fired when you've successfully registered for remote notifications with APNS. + fn registered_for_remote_notifications(&self, _token: &str) { + + } + + /// Fired after you've received a push notification from APNS. + //fn did_receive_remote_notification(&self, notification: PushNotification) {} + + /// Fired if there was a failure to register for remote notifications with APNS - e.g, + /// connection issues or something. + fn failed_to_register_for_remote_notifications(&self, _error: AppKitError) {} + /// Fires after the user accepted a CloudKit sharing invitation associated with your /// application. #[cfg(feature = "cloudkit")] diff --git a/appkit/src/bundle.rs b/appkit/src/bundle.rs index fe27304..4760a10 100644 --- a/appkit/src/bundle.rs +++ b/appkit/src/bundle.rs @@ -8,8 +8,6 @@ use std::ffi::CString; use std::mem; -use cocoa::foundation::{NSString}; -use cocoa::base::{id, nil, BOOL, YES};//, NO}; use objc::{class, msg_send, sel, sel_impl, Encode, Encoding, EncodeArguments, Message}; use objc::runtime::{Class, Sel, Method, Object, Imp}; use objc::runtime::{ @@ -19,6 +17,8 @@ use objc::runtime::{ method_exchangeImplementations }; +use crate::foundation::{id, nil, BOOL, YES, NSString}; + /// Types that can be used as the implementation of an Objective-C method. pub trait MethodImplementation { /// The callee type of the method. diff --git a/appkit/src/button.rs b/appkit/src/button.rs index 44a34ad..0ad3374 100644 --- a/appkit/src/button.rs +++ b/appkit/src/button.rs @@ -3,13 +3,12 @@ use std::sync::Once; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSString}; - use objc_id::Id; use objc::declare::ClassDecl; use objc::runtime::{Class, Object}; -use objc::{msg_send, sel, sel_impl}; +use objc::{class, msg_send, sel, sel_impl}; + +use crate::foundation::{nil, NSString}; /// A wrapper for `NSButton`. Holds (retains) pointers for the Objective-C runtime /// where our `NSButton` lives. @@ -21,10 +20,9 @@ impl Button { /// Creates a new `NSButton` instance, configures it appropriately, /// and retains the necessary Objective-C runtime pointer. pub fn new(text: &str) -> Self { + let title = NSString::new(text); let inner = unsafe { - let title = NSString::alloc(nil).init_str(text); - let button: id = msg_send![register_class(), buttonWithTitle:title target:nil action:nil]; - Id::from_ptr(button) + Id::from_ptr(msg_send![register_class(), buttonWithTitle:title target:nil action:nil]) }; Button { @@ -47,7 +45,7 @@ fn register_class() -> *const Class { static INIT: Once = Once::new(); INIT.call_once(|| unsafe { - let superclass = Class::get("NSButton").unwrap(); + let superclass = class!(NSButton); let decl = ClassDecl::new("RSTButton", superclass).unwrap(); VIEW_CLASS = decl.register(); }); diff --git a/appkit/src/color.rs b/appkit/src/color.rs index 8ccef9a..9d5660f 100644 --- a/appkit/src/color.rs +++ b/appkit/src/color.rs @@ -1,10 +1,10 @@ //! Implements `Color`. Heavily based on the `Color` module in Servo's CSS parser, but tweaked //! for (what I believe) is a friendlier API. -use cocoa::base::id; -use core_graphics::base::CGFloat; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, CGFloat}; + /// A color with red, green, blue, and alpha components, in a byte each. #[derive(Clone, Copy, PartialEq, Debug)] pub struct Color { diff --git a/appkit/src/dragdrop.rs b/appkit/src/dragdrop.rs index 5fed05c..58e80b5 100644 --- a/appkit/src/dragdrop.rs +++ b/appkit/src/dragdrop.rs @@ -2,12 +2,11 @@ //! across the codebase, hence why they're here - they're not currently exhaustive, so feel free to //! tinker and pull request. -use cocoa::foundation::NSUInteger; - use objc::runtime::Object; use objc::{msg_send, sel, sel_impl}; use objc_id::Id; +use crate::foundation::NSUInteger; use crate::pasteboard::Pasteboard; /// Represents operations that can happen for a given drag/drop scenario. diff --git a/appkit/src/error.rs b/appkit/src/error.rs index 588e70d..617de68 100644 --- a/appkit/src/error.rs +++ b/appkit/src/error.rs @@ -6,11 +6,9 @@ use std::error; use std::fmt; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSInteger, NSString}; use objc::{class, msg_send, sel, sel_impl}; -use crate::utils::str_from; +use crate::foundation::{id, nil, NSInteger, NSString}; /// A wrapper around pieces of data extracted from `NSError`. This could be improved: right now, it /// allocates `String` instances when theoretically it could be avoided, and we might be erasing @@ -29,16 +27,16 @@ impl AppKitError { pub fn new(error: id) -> Self { let (code, domain, description) = unsafe { let code: usize = msg_send![error, code]; - let domain: id = msg_send![error, domain]; - let description: id = msg_send![error, localizedDescription]; + let domain = NSString::wrap(msg_send![error, domain]); + let description = NSString::wrap(msg_send![error, localizedDescription]); (code, domain, description) }; AppKitError { code: code, - domain: str_from(domain).to_string(), - description: str_from(description).to_string() + domain: domain.to_str().to_string(), + description: description.to_str().to_string() } } @@ -51,7 +49,7 @@ impl AppKitError { /// thread safe. pub fn into_nserror(self) -> id { unsafe { - let domain = NSString::alloc(nil).init_str(&self.domain); + let domain = NSString::new(&self.domain); let code = self.code as NSInteger; msg_send![class!(NSError), errorWithDomain:domain code:code userInfo:nil] } diff --git a/appkit/src/events.rs b/appkit/src/events.rs index cb114f9..6793e9a 100644 --- a/appkit/src/events.rs +++ b/appkit/src/events.rs @@ -1,7 +1,7 @@ //! Hoists some type definitions in a way that I personally find cleaner than what's in the Servo //! code. -use cocoa::foundation::NSUInteger; +use crate::foundation::NSUInteger; #[derive(Clone, Copy, Debug)] pub enum EventModifierFlag { diff --git a/appkit/src/foundation/array.rs b/appkit/src/foundation/array.rs new file mode 100644 index 0000000..d1e8010 --- /dev/null +++ b/appkit/src/foundation/array.rs @@ -0,0 +1,82 @@ +//! A wrapper type for `NSArray`. This is abstracted out as we need to use `NSArray` in a ton of +//! instances in this framework, and down the road I'd like to investigate using `CFArray` instead +//! of `NSArray` (i.e, if the ObjC runtime is ever pulled or something - perhaps those types would +//! stick around). +//! +//! Essentially, consider this some sanity/cleanliness/future-proofing. End users should never need +//! to touch this. + +use objc::{class, msg_send, sel, sel_impl}; +use objc::runtime::Object; +use objc_id::Id; + +use crate::foundation::id; + +/// A wrapper for `NSArray` that makes common operations in our framework a bit easier to handle +/// and reason about. +#[derive(Debug)] +pub struct NSArray(pub Id); + +impl From> for NSArray { + /// Given a set of `Object`s, creates an `NSArray` that holds them. + fn from(objects: Vec<&Object>) -> Self { + NSArray(unsafe { + Id::from_ptr(msg_send![class!(NSArray), arrayWithObjects:objects.as_ptr() count:objects.len()]) + }) + } +} + +impl From> for NSArray { + /// Given a set of `*mut Object`s, creates an `NSArray` that holds them. + fn from(objects: Vec) -> Self { + NSArray(unsafe { + Id::from_ptr(msg_send![class!(NSArray), arrayWithObjects:objects.as_ptr() count:objects.len()]) + }) + } +} + +impl NSArray { + /// Given a set of `Object`s, creates an `NSArray` that holds them. + pub fn new(objects: &[id]) -> Self { + NSArray(unsafe { + Id::from_ptr(msg_send![class!(NSArray), arrayWithObjects:objects.as_ptr() count:objects.len()]) + }) + } + + /// In some cases, we're vended an `NSArray` by the system, and it's ideal to not retain that. + /// This handles that edge case. + pub fn wrap(array: id) -> Self { + NSArray(unsafe { + Id::from_retained_ptr(array) + }) + } + + /// Consumes and returns the underlying Objective-C value. + pub fn into_inner(mut self) -> id { + &mut *self.0 + } + + /// Returns the `count` (`len()` equivalent) for the backing `NSArray`. + pub fn count(&self) -> usize { + unsafe { msg_send![self.0, count] } + } + + /// A helper method for mapping over the backing `NSArray` items. + /// Often times we need to map in this framework to convert between Rust types, so isolating + /// this out makes life much easier. + pub fn map T>(&self, transform: F) -> Vec { + let count = self.count(); + let mut ret: Vec = Vec::with_capacity(count); + let mut index = 0; + + loop { + let item: id = unsafe { msg_send![&*self.0, objectAtIndex:index] }; + ret.push(transform(item)); + + index += 1; + if index == count { break } + } + + ret + } +} diff --git a/appkit/src/foundation/autoreleasepool.rs b/appkit/src/foundation/autoreleasepool.rs new file mode 100644 index 0000000..0f922b8 --- /dev/null +++ b/appkit/src/foundation/autoreleasepool.rs @@ -0,0 +1,19 @@ +//! A lightweight wrapper around `NSAutoreleasePool`. + +use objc::{class, msg_send, sel, sel_impl}; +use objc::runtime::Object; +use objc_id::Id; + +pub struct AutoReleasePool(pub Id); + +impl AutoReleasePool { + pub fn new() -> Self { + AutoReleasePool(unsafe { + Id::from_retained_ptr(msg_send![class!(NSAutoreleasePool), new]) + }) + } + + pub fn drain(self) { + let _: () = unsafe { msg_send![&*self.0, drain] }; + } +} diff --git a/appkit/src/foundation/dictionary.rs b/appkit/src/foundation/dictionary.rs new file mode 100644 index 0000000..919ca33 --- /dev/null +++ b/appkit/src/foundation/dictionary.rs @@ -0,0 +1,10 @@ +//! A wrapper for `NSDictionary`, which aims to make dealing with the class throughout this +//! framework a tad bit simpler. + +use objc::runtime::Object; +use objc_id::Id; + +#[derive(Debug)] +pub struct NSDictionary(Id); + +impl NSDictionary {} diff --git a/appkit/src/foundation/geometry.rs b/appkit/src/foundation/geometry.rs new file mode 100644 index 0000000..79ba500 --- /dev/null +++ b/appkit/src/foundation/geometry.rs @@ -0,0 +1,81 @@ +//! Implements Core Graphics Geometry types. Most of this is lifted from `servo/core-foundation-rs` +//! - as such, we include a copy of the license below. +//! +//! Copyright (c) 2012-2013 Mozilla Foundation +//! +//! Permission is hereby granted, free of charge, to any +//! person obtaining a copy of this software and associated +//! documentation files (the "Software"), to deal in the +//! Software without restriction, including without +//! limitation the rights to use, copy, modify, merge, +//! publish, distribute, sublicense, and/or sell copies of +//! the Software, and to permit persons to whom the Software +//! is furnished to do so, subject to the following +//! conditions: +//! +//! The above copyright notice and this permission notice +//! shall be included in all copies or substantial portions +//! of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +//! ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +//! TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +//! PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +//! SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +//! CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +//! OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +//! IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +//! DEALINGS IN THE SOFTWARE. + +use crate::foundation::CGFloat; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct CGSize { + pub width: CGFloat, + pub height: CGFloat, +} + +impl CGSize { + #[inline] + pub fn new(width: CGFloat, height: CGFloat) -> CGSize { + CGSize { + width: width, + height: height, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct CGPoint { + pub x: CGFloat, + pub y: CGFloat, +} + +impl CGPoint { + #[inline] + pub fn new(x: CGFloat, y: CGFloat) -> CGPoint { + CGPoint { + x: x, + y: y, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct CGRect { + pub origin: CGPoint, + pub size: CGSize +} + +impl CGRect { + #[inline] + pub fn new(origin: &CGPoint, size: &CGSize) -> CGRect { + CGRect { + origin: *origin, + size: *size, + } + } +} diff --git a/appkit/src/foundation/mod.rs b/appkit/src/foundation/mod.rs new file mode 100644 index 0000000..248d69c --- /dev/null +++ b/appkit/src/foundation/mod.rs @@ -0,0 +1,51 @@ +//! This module contains some lightweight wrappers over certain data types that we use throughout +//! the framework. Some of it is pulled/inspired from Servo's cocoa-rs (e.g, the "id" type). While +//! this isn't a clone of their module (we don't need everything from there, but remaining +//! compatible in case an end-user wants to drop that low is deal), it's worth linking their +//! license and repository - they've done really incredible work and it's 100% worth acknowledging. +//! +//! - [core-foundation-rs Repository](https://github.com/servo/core-foundation-rs) +//! - [core-foundation-rs MIT License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-MIT) +//! - [core-foundation-rs Apache License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-APACHE) + +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] + +use objc::runtime; +pub use objc::runtime::{BOOL, NO, YES}; + +pub mod autoreleasepool; +pub use autoreleasepool::AutoReleasePool; + +pub mod array; +pub use array::NSArray; + +pub mod string; +pub use string::NSString; + +pub mod dictionary; +pub use dictionary::NSDictionary; + +pub mod geometry; +pub use geometry::{CGSize, CGPoint, CGRect}; + +#[allow(non_camel_case_types)] +pub type id = *mut runtime::Object; + +#[allow(non_upper_case_globals)] +pub const nil: id = 0 as id; + +#[cfg(target_pointer_width = "32")] +pub type NSInteger = libc::c_int; +#[cfg(target_pointer_width = "32")] +pub type NSUInteger = libc::c_uint; + +#[cfg(target_pointer_width = "64")] +pub type NSInteger = libc::c_long; +#[cfg(target_pointer_width = "64")] +pub type NSUInteger = libc::c_ulong; + +#[cfg(target_pointer_width = "64")] +pub type CGFloat = libc::c_double; +#[cfg(not(target_pointer_width = "64"))] +pub type CGFloat = libc::c_float; diff --git a/appkit/src/foundation/string.rs b/appkit/src/foundation/string.rs new file mode 100644 index 0000000..d3de15e --- /dev/null +++ b/appkit/src/foundation/string.rs @@ -0,0 +1,56 @@ +//! A wrapper library for `NSString`, which we use throughout the framework. This is abstracted out +//! for a few reasons, but namely: +//! +//! - It's used often, so we want a decent enough API. +//! - Playing around with performance for this type is ideal, as it's a lot of heap allocation. +//! +//! End users should never need to interact with this. + +use std::{slice, str}; +use std::os::raw::c_char; + +use objc::{class, msg_send, sel, sel_impl}; +use objc::runtime::Object; +use objc_id::Id; + +use crate::foundation::id; + +const UTF8_ENCODING: usize = 4; + +/// Wraps an underlying `NSString`. +#[derive(Debug)] +pub struct NSString(pub Id); + +impl NSString { + pub fn new(s: &str) -> Self { + NSString(unsafe { + let nsstring: *mut Object = msg_send![class!(NSString), alloc]; + //msg_send![nsstring, initWithBytesNoCopy:s.as_ptr() length:s.len() encoding:4 freeWhenDone:NO] + Id::from_ptr(msg_send![nsstring, initWithBytes:s.as_ptr() length:s.len() encoding:UTF8_ENCODING]) + }) + } + + pub fn wrap(object: id) -> Self { + NSString(unsafe { + Id::from_retained_ptr(object) + }) + } + + pub fn into_inner(mut self) -> id { + &mut *self.0 + } + + /// A utility method for taking an `NSString` and bridging it to a Rust `&str`. + pub fn to_str(self) -> &'static str { + unsafe { + let bytes = { + let bytes: *const c_char = msg_send![&*self.0, UTF8String]; + bytes as *const u8 + }; + + let len = msg_send![&*self.0, lengthOfBytesUsingEncoding:UTF8_ENCODING]; + let bytes = slice::from_raw_parts(bytes, len); + str::from_utf8(bytes).unwrap() + } + } +} diff --git a/appkit/src/geometry.rs b/appkit/src/geometry.rs index a42b4fd..7094f3b 100644 --- a/appkit/src/geometry.rs +++ b/appkit/src/geometry.rs @@ -1,6 +1,6 @@ //! Wrapper methods for various geometry types (rects, sizes, ec). -use cocoa::foundation::{NSRect, NSPoint, NSSize}; +use crate::foundation::{CGRect, CGPoint, CGSize}; /// A struct that represents a box - top, left, width and height. pub struct Rect { @@ -29,11 +29,11 @@ impl Rect { } } -impl From for NSRect { - fn from(rect: Rect) -> NSRect { - NSRect::new( - NSPoint::new(rect.top, rect.left), - NSSize::new(rect.width, rect.height) +impl From for CGRect { + fn from(rect: Rect) -> CGRect { + CGRect::new( + &CGPoint::new(rect.top, rect.left), + &CGSize::new(rect.width, rect.height) ) } } diff --git a/appkit/src/layout/constraint.rs b/appkit/src/layout/constraint.rs index 35b2473..f31b748 100644 --- a/appkit/src/layout/constraint.rs +++ b/appkit/src/layout/constraint.rs @@ -2,13 +2,12 @@ //! escape hatch, if you need it (we use it for things like width and height, which aren't handled //! by an axis). -use cocoa::base::id; -use core_graphics::base::CGFloat; - use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::{id, CGFloat}; + /// A wrapper for `NSLayoutConstraint`. This both acts as a central path through which to activate /// constraints, as well as a wrapper for layout constraints that are not axis bound (e.g, width or /// height). diff --git a/appkit/src/layout/dimension.rs b/appkit/src/layout/dimension.rs index d20a1d7..510eff3 100644 --- a/appkit/src/layout/dimension.rs +++ b/appkit/src/layout/dimension.rs @@ -1,13 +1,11 @@ //! A wrapper for `NSLayoutAnchorDimension`, which is typically used to handle `width` and `height` //! values for how a given view should layout. -use cocoa::base::id; -use core_graphics::base::CGFloat; - use objc::{msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::{id, CGFloat}; use crate::layout::constraint::LayoutConstraint; /// A wrapper for `NSLayoutAnchor`. You should never be creating this yourself - it's more of a diff --git a/appkit/src/layout/horizontal.rs b/appkit/src/layout/horizontal.rs index 1af46c9..93e607c 100644 --- a/appkit/src/layout/horizontal.rs +++ b/appkit/src/layout/horizontal.rs @@ -2,12 +2,11 @@ //! given view should layout along the x-axis. Of note: the only thing that can't be protected //! against is mixing/matching incorrect left/leading and right/trailing anchors. Be careful! -use cocoa::base::id; - use objc::{msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::id; use crate::layout::constraint::LayoutConstraint; /// A wrapper for `NSLayoutAnchor`. You should never be creating this yourself - it's more of a diff --git a/appkit/src/layout/vertical.rs b/appkit/src/layout/vertical.rs index 2e1b197..ad834a6 100644 --- a/appkit/src/layout/vertical.rs +++ b/appkit/src/layout/vertical.rs @@ -2,12 +2,11 @@ //! given view should layout along the x-axis. Of note: the only thing that can't be protected //! against is mixing/matching incorrect left/leading and right/trailing anchors. Be careful! -use cocoa::base::id; - use objc::{msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::id; use crate::layout::constraint::LayoutConstraint; /// A wrapper for `NSLayoutAnchor`. You should never be creating this yourself - it's more of a diff --git a/appkit/src/lib.rs b/appkit/src/lib.rs index 4d67db0..c29925d 100644 --- a/appkit/src/lib.rs +++ b/appkit/src/lib.rs @@ -16,15 +16,11 @@ //! your own risk. With that said, provided you follow the rules (regarding memory/ownership) it's //! already fine for some apps. Check the README for more info! -pub use objc_id::ShareId; -pub use objc::runtime::Object; -pub use cocoa::base::id; - pub mod alert; pub mod app; pub mod button; -#[cfg(feature = "user-notifications")] +#[cfg(feature = "cloudkit")] pub mod cloudkit; pub mod color; @@ -33,7 +29,8 @@ pub mod constants; pub mod dragdrop; pub mod error; pub mod events; -pub mod filesystem; +//pub mod filesystem; +pub mod foundation; pub mod geometry; pub mod layout; pub mod menu; @@ -47,7 +44,7 @@ pub mod printing; pub mod toolbar; pub mod user_activity; pub mod utils; -pub mod view; +/*pub mod view; pub mod webview; pub mod window; @@ -81,4 +78,4 @@ pub mod prelude { pub use appkit_derive::{ WindowWrapper, ViewWrapper }; -} +}*/ diff --git a/appkit/src/menu/item.rs b/appkit/src/menu/item.rs index 5d6dd17..420a212 100644 --- a/appkit/src/menu/item.rs +++ b/appkit/src/menu/item.rs @@ -2,13 +2,11 @@ //! one level deep; this could change in the future but is fine for //! now. -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSString, NSUInteger}; - use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::{Object, Sel}; use objc_id::ShareId; +use crate::foundation::{id, nil, NSString, NSUInteger}; use crate::events::EventModifierFlag; /// Internal method (shorthand) for generating `NSMenuItem` holders. @@ -21,10 +19,10 @@ fn make_menu_item( unsafe { let cls = class!(NSMenuItem); let alloc: id = msg_send![cls, alloc]; - let title = NSString::alloc(nil).init_str(title); + let title = NSString::new(title); // Note that AppKit requires a blank string if nil, not nil. - let key = NSString::alloc(nil).init_str(match key { + let key = NSString::new(match key { Some(s) => s, None => "" }); @@ -74,7 +72,7 @@ impl MenuItem { MenuItem::Action(item) => { unsafe { - let key = NSString::alloc(nil).init_str(key); + let key = NSString::new(key); let _: () = msg_send![&*item, setKeyEquivalent:key]; } diff --git a/appkit/src/menu/menu.rs b/appkit/src/menu/menu.rs index 309e181..17a623d 100644 --- a/appkit/src/menu/menu.rs +++ b/appkit/src/menu/menu.rs @@ -1,12 +1,10 @@ //! Wraps NSMenu and handles instrumenting necessary delegate pieces. -use cocoa::base::{id, nil}; -use cocoa::foundation::NSString; - use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, NSString}; use crate::menu::item::MenuItem; /// A struct that represents an `NSMenu`. It takes ownership of items, and handles instrumenting @@ -23,7 +21,7 @@ impl Menu { let inner = unsafe { let cls = class!(NSMenu); let alloc: id = msg_send![cls, alloc]; - let title = NSString::alloc(nil).init_str(title); + let title = NSString::new(title); let inner: id = msg_send![alloc, initWithTitle:title]; Id::from_ptr(inner) }; diff --git a/appkit/src/networking/mod.rs b/appkit/src/networking/mod.rs index d7bf120..bcc3c33 100644 --- a/appkit/src/networking/mod.rs +++ b/appkit/src/networking/mod.rs @@ -1,13 +1,11 @@ //! A lightweight wrapper over some networking components, like `NSURLRequest` and co. //! This is currently not meant to be exhaustive. -use cocoa::base::id; -use objc_id::Id; - use objc::{msg_send, sel, sel_impl}; use objc::runtime::Object; +use objc_id::Id; -use crate::utils::str_from; +use crate::foundation::{id, NSString}; pub struct URLRequest { pub inner: Id @@ -21,10 +19,9 @@ impl URLRequest { } pub fn url(&self) -> &'static str { - unsafe { + NSString::wrap(unsafe { let url: id = msg_send![&*self.inner, URL]; - let path: id = msg_send![url, absoluteString]; - str_from(path) - } + msg_send![url, absoluteString] + }).to_str() } } diff --git a/appkit/src/pasteboard/mod.rs b/appkit/src/pasteboard/mod.rs index 01c2099..74cb489 100644 --- a/appkit/src/pasteboard/mod.rs +++ b/appkit/src/pasteboard/mod.rs @@ -1,6 +1,96 @@ +//! A wrapper for NSPasteBoard, which is the interface for copy/paste and general transferring +//! (think: drag and drop between applications). It exposes a Rust interface that tries to be +//! complete, but might not cover everything 100% right now - feel free to pull request. + +use std::error::Error; + +use objc::runtime::Object; +use objc::{class, msg_send, sel, sel_impl}; +use objc_id::Id; +use url::Url; + +use crate::foundation::{id, nil, NSString, NSArray}; +use crate::error::AppKitError; pub mod types; -pub use types::*; +pub use types::{PasteboardName, PasteboardType}; -pub mod pasteboard; -pub use pasteboard::*; +/// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop. +pub struct Pasteboard(pub Id); + +impl Default for Pasteboard { + fn default() -> Self { + Pasteboard(unsafe { + Id::from_retained_ptr(msg_send![class!(NSPasteboard), generalPasteboard]) + }) + } +} + +impl Pasteboard { + /// Used internally for wrapping a Pasteboard returned from operations (say, drag and drop). + pub(crate) fn with(existing: id) -> Self { + Pasteboard(unsafe { + Id::from_retained_ptr(existing) + }) + } + + /// Retrieves the system Pasteboard for the given name/type. + pub fn named(name: PasteboardName) -> Self { + Pasteboard(unsafe { + let name: NSString = name.into(); + Id::from_retained_ptr(msg_send![class!(NSPasteboard), pasteboardWithName:&*name.0]) + }) + } + + /// Creates and returns a new pasteboard with a name that is guaranteed to be unique with + /// respect to other pasteboards in the system. + pub fn unique() -> Self { + Pasteboard(unsafe { + Id::from_ptr(msg_send![class!(NSPasteboard), pasteboardWithUniqueName]) + }) + } + + /// Releases the receiver’s resources in the pasteboard server. It's rare-ish to need to use + /// this, but considering this stuff happens on the Objective-C side you may need it. + pub fn release_globally(&self) { + unsafe { + let _: () = msg_send![&*self.0, releaseGlobally]; + } + } + + /// Clears the existing contents of the pasteboard. + pub fn clear_contents(&self) { + unsafe { + let _: () = msg_send![&*self.0, clearContents]; + } + } + + /// Looks inside the pasteboard contents and extracts what FileURLs are there, if any. + pub fn get_file_urls(&self) -> Result, Box> { + unsafe { + let class: id = msg_send![class!(NSURL), class]; + let classes = NSArray::new(&[class]); + let contents: id = msg_send![&*self.0, readObjectsForClasses:classes options:nil]; + + // This can happen if the Pasteboard server has an error in returning items. + // In our case, we'll bubble up an error by checking the pasteboard. + if contents == nil { + // This error is not necessarily "correct", but in the event of an error in + // Pasteboard server retrieval I'm not sure where to check... and this stuff is + // kinda ancient and has conflicting docs in places. ;P + return Err(Box::new(AppKitError { + code: 666, + domain: "com.appkit-rs.pasteboard".to_string(), + description: "Pasteboard server returned no data.".to_string() + })); + } + + let urls = NSArray::wrap(contents).map(|url| { + let path = NSString::wrap(msg_send![url, path]); + Url::parse(&format!("file://{}", path.to_str())) + }).into_iter().filter_map(|r| r.ok()).collect(); + + Ok(urls) + } + } +} diff --git a/appkit/src/pasteboard/pasteboard.rs b/appkit/src/pasteboard/pasteboard.rs deleted file mode 100644 index 0ae054b..0000000 --- a/appkit/src/pasteboard/pasteboard.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! A wrapper for NSPasteBoard, which is the interface for copy/paste and general transferring -//! (think: drag and drop between applications). It exposes a Rust interface that tries to be -//! complete, but might not cover everything 100% right now - feel free to pull request. - -use std::error::Error; - -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSArray}; -use objc::runtime::Object; -use objc::{class, msg_send, sel, sel_impl}; -use objc_id::Id; -use url::Url; - -use crate::error::AppKitError; -use crate::pasteboard::types::{PasteboardName}; -use crate::utils::str_from; - -/// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop. -pub struct Pasteboard { - /// The internal pointer to the Objective-C side. - pub inner: Id -} - -impl Default for Pasteboard { - fn default() -> Self { - Pasteboard { - inner: unsafe { Id::from_ptr(msg_send![class!(NSPasteboard), generalPasteboard]) } - } - } -} - -impl Pasteboard { - /// Used internally for wrapping a Pasteboard returned from operations (say, drag and drop). - pub(crate) fn with(existing: id) -> Self { - Pasteboard { - inner: unsafe { Id::from_ptr(existing) } - } - } - - /// Retrieves the system Pasteboard for the given name/type. - pub fn named(name: PasteboardName) -> Self { - Pasteboard { - inner: unsafe { - let name = name.to_nsstring(); - Id::from_ptr(msg_send![class!(NSPasteboard), pasteboardWithName:name]) - } - } - } - - /// Creates and returns a new pasteboard with a name that is guaranteed to be unique with - /// respect to other pasteboards in the system. - pub fn unique() -> Self { - Pasteboard { - inner: unsafe { Id::from_ptr(msg_send![class!(NSPasteboard), pasteboardWithUniqueName]) } - } - } - - /// Releases the receiver’s resources in the pasteboard server. It's rare-ish to need to use - /// this, but considering this stuff happens on the Objective-C side you may need it. - pub fn release_globally(&self) { - unsafe { - let _: () = msg_send![&*self.inner, releaseGlobally]; - } - } - - /// Clears the existing contents of the pasteboard. - pub fn clear_contents(&self) { - unsafe { - let _: () = msg_send![&*self.inner, clearContents]; - } - } - - /// Looks inside the pasteboard contents and extracts what FileURLs are there, if any. - pub fn get_file_urls(&self) -> Result, Box> { - unsafe { - let mut i = 0; - - let class: id = msg_send![class!(NSURL), class]; - let classes: id = NSArray::arrayWithObjects(nil, &[class]); - let contents: id = msg_send![&*self.inner, readObjectsForClasses:classes options:nil]; - - // This can happen if the Pasteboard server has an error in returning items. - // In our case, we'll bubble up an error by checking the pasteboard. - if contents == nil { - // This error is not necessarily "correct", but in the event of an error in - // Pasteboard server retrieval I'm not sure where to check... and this stuff is - // kinda ancient and has conflicting docs in places. ;P - return Err(Box::new(AppKitError { - code: 666, - domain: "com.appkit-rs.pasteboard".to_string(), - description: "Pasteboard server returned no data.".to_string() - })); - } - - let count: usize = msg_send![contents, count]; - let mut urls: Vec = Vec::with_capacity(count); - - loop { - let nsurl: id = msg_send![contents, objectAtIndex:i]; - let path: id = msg_send![nsurl, path]; - let s = str_from(path); - urls.push(Url::parse(&format!("file://{}", s))?); - - i += 1; - if i == count { break; } - } - - Ok(urls) - } - } -/* - /// Retrieves the pasteboard contents as a string. This can be `None` (`nil` on the Objective-C - /// side) if the pasteboard data doesn't match the requested type, so check accordingly. - /// - /// Note: In macOS 10.6 and later, if the receiver contains multiple items that can provide string, - /// RTF, or RTFD data, the text data from each item is returned as a combined result separated by newlines. - /// This Rust wrapper is a quick pass, and could be improved. ;P - pub fn contents_for(&self, pasteboard_type: PasteboardType) -> Option { - unsafe { - let contents: id = msg_send![&*self.inner, stringForType:pasteboard_type.to_nsstring()]; - if contents != nil { - return Some(str_from(contents).to_string()); - } - } - - None - }*/ -} diff --git a/appkit/src/pasteboard/types.rs b/appkit/src/pasteboard/types.rs index 74abc0c..b369fa4 100644 --- a/appkit/src/pasteboard/types.rs +++ b/appkit/src/pasteboard/types.rs @@ -1,8 +1,7 @@ //! This module provides some basic wrappers for Pasteboard functionality. It's currently not an //! exhaustive clone, but feel free to pull request accordingly! -use cocoa::base::{id, nil}; -use cocoa::foundation::NSString; +use crate::foundation::NSString; /// Constants for the standard system pasteboard names. #[derive(Debug, Copy, Clone)] @@ -23,18 +22,15 @@ pub enum PasteboardName { Ruler } -impl PasteboardName { - /// Creates an `NSString` out of the underlying type. - pub fn to_nsstring(&self) -> id { - unsafe { - NSString::alloc(nil).init_str(match self { - PasteboardName::Drag => "Apple CFPasteboard drag", - PasteboardName::Find => "Apple CFPasteboard find", - PasteboardName::Font => "Apple CFPasteboard font", - PasteboardName::General => "Apple CFPasteboard general", - PasteboardName::Ruler => "Apple CFPasteboard ruler" - }) - } +impl From for NSString { + fn from(name: PasteboardName) -> Self { + NSString::new(match name { + PasteboardName::Drag => "Apple CFPasteboard drag", + PasteboardName::Find => "Apple CFPasteboard find", + PasteboardName::Font => "Apple CFPasteboard font", + PasteboardName::General => "Apple CFPasteboard general", + PasteboardName::Ruler => "Apple CFPasteboard ruler" + }) } } @@ -87,27 +83,24 @@ pub enum PasteboardType { TIFF } -impl PasteboardType { - /// Creates an `NSString` out of the underlying type. - pub fn to_nsstring(&self) -> id { - unsafe { - NSString::alloc(nil).init_str(match self { - PasteboardType::URL => "public.url", - PasteboardType::Color => "com.apple.cocoa.pasteboard.color", - PasteboardType::FileURL => "public.file-url", - PasteboardType::Font => "com.apple.cocoa.pasteboard.character-formatting", - PasteboardType::HTML => "public.html", - PasteboardType::MultipleTextSelection => "com.apple.cocoa.pasteboard.multiple-text-selection", - PasteboardType::PDF => "com.adobe.pdf", - PasteboardType::PNG => "public.png", - PasteboardType::RTF => "public.rtf", - PasteboardType::RTFD => "com.apple.flat-rtfd", - PasteboardType::Ruler => "com.apple.cocoa.pasteboard.paragraph-formatting", - PasteboardType::Sound => "com.apple.cocoa.pasteboard.sound", - PasteboardType::String => "public.utf8-plain-text", - PasteboardType::TabularText => "public.utf8-tab-separated-values-text", - PasteboardType::TIFF => "public.tiff", - }) - } +impl From for NSString { + fn from(pboard_type: PasteboardType) -> Self { + NSString::new(match pboard_type { + PasteboardType::URL => "public.url", + PasteboardType::Color => "com.apple.cocoa.pasteboard.color", + PasteboardType::FileURL => "public.file-url", + PasteboardType::Font => "com.apple.cocoa.pasteboard.character-formatting", + PasteboardType::HTML => "public.html", + PasteboardType::MultipleTextSelection => "com.apple.cocoa.pasteboard.multiple-text-selection", + PasteboardType::PDF => "com.adobe.pdf", + PasteboardType::PNG => "public.png", + PasteboardType::RTF => "public.rtf", + PasteboardType::RTFD => "com.apple.flat-rtfd", + PasteboardType::Ruler => "com.apple.cocoa.pasteboard.paragraph-formatting", + PasteboardType::Sound => "com.apple.cocoa.pasteboard.sound", + PasteboardType::String => "public.utf8-plain-text", + PasteboardType::TabularText => "public.utf8-tab-separated-values-text", + PasteboardType::TIFF => "public.tiff", + }) } } diff --git a/appkit/src/printing/enums.rs b/appkit/src/printing/enums.rs new file mode 100644 index 0000000..427cd09 --- /dev/null +++ b/appkit/src/printing/enums.rs @@ -0,0 +1,32 @@ +//! Enums used through the general printing flow. + +use crate::foundation::NSUInteger; + +/// Used for handling printing files. You return this in relevant `AppController` methods. +#[derive(Copy, Clone, Debug)] +pub enum PrintResponse { + /// Printing was cancelled. + Cancelled, + + /// Printing was a success. + Success, + + /// Printing failed. + Failure, + + /// For when the result of printing cannot be returned immediately (e.g, if printing causes a sheet to appear). + /// If your method returns PrintResponse::ReplyLater it must always invoke `App::reply_to_open_or_print()` when the + /// entire print operation has been completed, successfully or not. + ReplyLater +} + +impl From for NSUInteger { + fn from(response: PrintResponse) -> NSUInteger { + match response { + PrintResponse::Cancelled => 0, + PrintResponse::Success => 1, + PrintResponse::Failure => 3, + PrintResponse::ReplyLater => 2 + } + } +} diff --git a/appkit/src/printing/mod.rs b/appkit/src/printing/mod.rs index 408871b..a91a453 100644 --- a/appkit/src/printing/mod.rs +++ b/appkit/src/printing/mod.rs @@ -1,5 +1,8 @@ //! Implements types used for printing (both configuring print jobs, as well as the act of printing //! itself). +pub mod enums; +pub use enums::PrintResponse; + pub mod settings; pub use settings::PrintSettings; diff --git a/appkit/src/printing/settings.rs b/appkit/src/printing/settings.rs index 1311558..df40dba 100644 --- a/appkit/src/printing/settings.rs +++ b/appkit/src/printing/settings.rs @@ -1,10 +1,11 @@ //! Represents settings for printing items. Backed by an `NSDictionary` in Objective-C, this struct //! aims to make it easier to query/process printing operations. -use cocoa::base::id; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::id; + /// `PrintSettings` represents options used in printing, typically passed to you by the /// application/user. #[derive(Clone, Debug)] diff --git a/appkit/src/toolbar/class.rs b/appkit/src/toolbar/class.rs index b05e996..705c976 100644 --- a/appkit/src/toolbar/class.rs +++ b/appkit/src/toolbar/class.rs @@ -3,58 +3,51 @@ use std::rc::Rc; use std::sync::Once; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSArray, NSString}; - use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, sel, sel_impl}; +use crate::foundation::{id, NSArray, NSString}; use crate::constants::TOOLBAR_PTR; use crate::toolbar::traits::ToolbarController; -use crate::utils::{load, str_from}; +use crate::utils::load; /// Retrieves and passes the allowed item identifiers for this toolbar. extern fn allowed_item_identifiers(this: &Object, _: Sel, _: id) -> id { let toolbar = load::(this, TOOLBAR_PTR); - unsafe { - let identifiers = { - let t = toolbar.borrow(); + let identifiers: NSArray = { + let t = toolbar.borrow(); + (*t).allowed_item_identifiers().iter().map(|identifier| { + NSString::new(identifier).into_inner() + }).collect::>().into() + }; - (*t).allowed_item_identifiers().iter().map(|identifier| { - NSString::alloc(nil).init_str(identifier) - }).collect::>() - }; - - Rc::into_raw(toolbar); - NSArray::arrayWithObjects(nil, &identifiers) - } + Rc::into_raw(toolbar); + identifiers.into_inner() } /// Retrieves and passes the default item identifiers for this toolbar. extern fn default_item_identifiers(this: &Object, _: Sel, _: id) -> id { let toolbar = load::(this, TOOLBAR_PTR); - unsafe { - let identifiers = { - let t = toolbar.borrow(); - - (*t).default_item_identifiers().iter().map(|identifier| { - NSString::alloc(nil).init_str(identifier) - }).collect::>() - }; + let identifiers: NSArray = { + let t = toolbar.borrow(); + + (*t).default_item_identifiers().iter().map(|identifier| { + NSString::new(identifier).into_inner() + }).collect::>().into() + }; - Rc::into_raw(toolbar); - NSArray::arrayWithObjects(nil, &identifiers) - } + Rc::into_raw(toolbar); + identifiers.into_inner() } /// Loads the controller, grabs whatever item is for this identifier, and returns what the /// Objective-C runtime needs. extern fn item_for_identifier(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id { let toolbar = load::(this, TOOLBAR_PTR); - let identifier = str_from(identifier); + let identifier = NSString::wrap(identifier).to_str(); let mut item = { let t = toolbar.borrow(); diff --git a/appkit/src/toolbar/handle.rs b/appkit/src/toolbar/handle.rs index 48175ab..1f5f4bf 100644 --- a/appkit/src/toolbar/handle.rs +++ b/appkit/src/toolbar/handle.rs @@ -1,13 +1,11 @@ //! A wrapper for the underlying `NSToolbar`, which is safe to clone and pass around. We do this to //! provide a uniform and expectable API. -use cocoa::base::{YES, NO}; -use cocoa::foundation::{NSUInteger}; - use objc::{msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::{YES, NO, NSUInteger}; use crate::toolbar::types::{ToolbarDisplayMode, ToolbarSizeMode}; #[derive(Clone, Debug)] diff --git a/appkit/src/toolbar/item.rs b/appkit/src/toolbar/item.rs index 4150a80..7bfc723 100644 --- a/appkit/src/toolbar/item.rs +++ b/appkit/src/toolbar/item.rs @@ -3,13 +3,11 @@ //! //! UNFORTUNATELY, this is a very old and janky API. So... yeah. -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSSize, NSString}; - use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, CGSize, NSString}; use crate::button::Button; /// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime @@ -28,7 +26,7 @@ impl ToolbarItem { let identifier = identifier.into(); let inner = unsafe { - let identifr = NSString::alloc(nil).init_str(&identifier); + let identifr = NSString::new(&identifier); let alloc: id = msg_send![class!(NSToolbarItem), alloc]; let item: id = msg_send![alloc, initWithItemIdentifier:identifr]; Id::from_ptr(item) @@ -44,7 +42,7 @@ impl ToolbarItem { /// Sets the title for this item. pub fn set_title(&mut self, title: &str) { unsafe { - let title = NSString::alloc(nil).init_str(title); + let title = NSString::new(title); let _: () = msg_send![&*self.inner, setTitle:title]; } } @@ -63,7 +61,7 @@ impl ToolbarItem { /// Sets the minimum size for this button. pub fn set_min_size(&mut self, width: f64, height: f64) { unsafe { - let size = NSSize::new(width.into(), height.into()); + let size = CGSize::new(width.into(), height.into()); let _: () = msg_send![&*self.inner, setMinSize:size]; } } @@ -71,7 +69,7 @@ impl ToolbarItem { /// Sets the maximum size for this button. pub fn set_max_size(&mut self, width: f64, height: f64) { unsafe { - let size = NSSize::new(width.into(), height.into()); + let size = CGSize::new(width.into(), height.into()); let _: () = msg_send![&*self.inner, setMaxSize:size]; } } diff --git a/appkit/src/toolbar/toolbar.rs b/appkit/src/toolbar/toolbar.rs index 02129ac..7a60097 100644 --- a/appkit/src/toolbar/toolbar.rs +++ b/appkit/src/toolbar/toolbar.rs @@ -6,12 +6,10 @@ use std::cell::RefCell; use std::rc::Rc; -use cocoa::base::{id, nil}; -use cocoa::foundation::NSString; - use objc_id::ShareId; use objc::{msg_send, sel, sel_impl}; +use crate::foundation::{id, NSString}; use crate::constants::TOOLBAR_PTR; use crate::toolbar::class::register_toolbar_class; use crate::toolbar::handle::ToolbarHandle; @@ -49,7 +47,7 @@ impl Toolbar where T: ToolbarController + 'static { let objc_controller = unsafe { let delegate_class = register_toolbar_class::(); - let identifier = NSString::alloc(nil).init_str(&identifier); + let identifier = NSString::new(&identifier); let alloc: id = msg_send![delegate_class, alloc]; let toolbar: id = msg_send![alloc, initWithIdentifier:identifier]; diff --git a/appkit/src/toolbar/types.rs b/appkit/src/toolbar/types.rs index fd8f889..edd20fd 100644 --- a/appkit/src/toolbar/types.rs +++ b/appkit/src/toolbar/types.rs @@ -1,6 +1,6 @@ //! Various types used for Toolbar configuration. -use cocoa::foundation::NSUInteger; +use crate::foundation::NSUInteger; /// Represents the display mode(s) a Toolbar can render in. #[derive(Clone, Copy, Debug)] diff --git a/appkit/src/user_activity.rs b/appkit/src/user_activity.rs index d131288..daa1fca 100644 --- a/appkit/src/user_activity.rs +++ b/appkit/src/user_activity.rs @@ -1,9 +1,10 @@ //! A module wrapping `NSUserActivity`. -use cocoa::base::id; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::id; + /// Represents an `NSUserActivity`, which acts as a lightweight method to capture the state of your /// app. pub struct UserActivity { diff --git a/appkit/src/utils.rs b/appkit/src/utils.rs index 5f00b01..59bfcb9 100644 --- a/appkit/src/utils.rs +++ b/appkit/src/utils.rs @@ -4,51 +4,9 @@ use std::rc::Rc; use std::cell::RefCell; -use std::{slice, str}; -use std::os::raw::c_char; -use cocoa::base::id; -use cocoa::foundation::NSString; - -use objc::{msg_send, sel, sel_impl}; use objc::runtime::Object; -/// A utility method for taking an `NSString` and bridging it to a Rust `&str`. -pub fn str_from(nsstring: id) -> &'static str { - unsafe { - let bytes = { - let bytes: *const c_char = msg_send![nsstring, UTF8String]; - bytes as *const u8 - }; - - let len = nsstring.len(); - let bytes = slice::from_raw_parts(bytes, len); - str::from_utf8(bytes).unwrap() - } -} - -/// A utility method for mapping over NSArray instances. There's a number of places where we want -/// or need this functionality to provide Rust interfaces - this tries to do it in a way where the -/// `Vec` doesn't need to resize after being allocated. -pub fn map_nsarray(array: id, transform: F) -> Vec -where F: Fn(id) -> T { - let count: usize = unsafe { msg_send![array, count] }; - - let mut ret: Vec = Vec::with_capacity(count); - let mut index = 0; - - loop { - let file: id = unsafe { msg_send![array, objectAtIndex:index] }; - ret.push(transform(file)); - - index += 1; - if index == count { break } - } - - ret -} - - /// Used for moving a pointer back into an Rc, so we can work with the object held behind it. Note /// that it's very important to make sure you reverse this when you're done (using /// `Rc::into_raw()`) otherwise you'll cause problems due to the `Drop` logic. diff --git a/appkit/src/webview/process_pool.rs b/appkit/src/webview/process_pool.rs index 31ad0c1..9f27c82 100644 --- a/appkit/src/webview/process_pool.rs +++ b/appkit/src/webview/process_pool.rs @@ -9,13 +9,13 @@ use std::ffi::c_void; use block::Block; -use cocoa::base::{id, nil, YES, NO}; use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSArray, NSInteger}; use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel, BOOL}; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, nil, YES, NO}; use crate::webview::traits::WebViewController; extern fn download_delegate(this: &Object, _: Sel) -> id { diff --git a/appkit/src/webview/webview.rs b/appkit/src/webview/webview.rs index 4fa90ec..51a4308 100644 --- a/appkit/src/webview/webview.rs +++ b/appkit/src/webview/webview.rs @@ -11,13 +11,11 @@ use std::rc::Rc; use std::cell::RefCell; -use cocoa::base::{id, nil, YES, NO}; -use cocoa::foundation::NSString; - use objc_id::ShareId; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, nil, YES, NO, NSString}; use crate::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; use crate::view::ViewController; use crate::webview::config::{WebViewConfig, InjectAt};