Further work on finishing up AppController interface

This commit is contained in:
Ryan McGrath 2020-03-15 23:10:43 -07:00
parent f1689d7cf9
commit 7b5ed88bb1
No known key found for this signature in database
GPG key ID: 811674B62B666830
12 changed files with 143 additions and 54 deletions

View file

@ -2,8 +2,11 @@
//! creates a custom `NSApplication` subclass that currently does nothing; this is meant as a hook
//! for potential future use.
use std::unreachable;
use std::ffi::c_void;
use std::sync::Once;
use std::unreachable;
use block::Block;
use cocoa::base::{id, nil, BOOL, YES, NO};
use cocoa::foundation::{NSUInteger};
@ -18,9 +21,11 @@ use crate::app::traits::AppController;
use crate::constants::APP_PTR;
use crate::error::AppKitError;
use crate::printing::PrintSettings;
use crate::utils::str_from;
use crate::user_activity::UserActivity;
use crate::utils::{map_nsarray, str_from};
/// A handy method for grabbing our `AppController` from the pointer.
/// A handy method for grabbing our `AppController` from the pointer. This is different from our
/// standard `utils` version as this doesn't require `RefCell` backing.
fn app<T: AppController>(this: &Object) -> &T {
unsafe {
let app_ptr: usize = *this.get_ivar(APP_PTR);
@ -113,14 +118,18 @@ extern fn should_handle_reopen<T: AppController>(this: &Object, _: Sel, _: id, h
}
/// Fires when the application delegate receives a `applicationDockMenu:` request.
extern fn dock_menu<T: AppController>(_this: &Object, _: Sel, _: id) -> id {
nil
extern fn dock_menu<T: AppController>(this: &Object, _: Sel, _: id) -> id {
// @TODO: Confirm this is safe to do and not leaky.
match app::<T>(this).dock_menu() {
Some(mut menu) => &mut *menu.inner,
None => nil
}
}
/// Fires when the application delegate receives a `application:willPresentError:` notification.
extern fn will_present_error<T: AppController>(this: &Object, _: Sel, _: id, error: id) -> id {
let error = AppKitError::new(error);
app::<T>(this).will_present_error(*error).into_nserror()
app::<T>(this).will_present_error(error).into_nserror()
}
/// Fires when the application receives a `applicationDidChangeScreenParameters:` notification.
@ -130,30 +139,44 @@ extern fn did_change_screen_parameters<T: AppController>(this: &Object, _: Sel,
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
/// notification.
extern fn will_continue_user_activity_with_type<T: AppController>(_this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
extern fn will_continue_user_activity_with_type<T: AppController>(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
let activity = str_from(activity_type);
NO
/*match app::<T>(this).will_continue_user_activity_with_type(activity) {
match app::<T>(this).will_continue_user_activity(activity) {
true => YES,
false => NO
}*/
}
}
/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification.
extern fn continue_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, _: id, _: id) -> BOOL {
NO
extern fn continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL {
// @TODO: This needs to support restorable objects, but it involves a larger question about how
// much `NSObject` wrapping we want to do here. For now, pass the handler for whenever it's
// useful.
let activity = UserActivity::with_inner(activity);
match app::<T>(this).continue_user_activity(activity, || unsafe {
let handler = handler as *const Block<(id,), c_void>;
(*handler).call((nil,));
}) {
true => YES,
false => NO
}
}
/// Fires when the application receives a
/// `application:didFailToContinueUserActivityWithType:error:` message.
extern fn failed_to_continue_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, activity_type: id, error: id) {
extern fn failed_to_continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity_type: id, error: id) {
app::<T>(this).failed_to_continue_user_activity(
str_from(activity_type),
AppKitError::new(error)
);
}
/// Fires when the application receives a `application:didUpdateUserActivity:` message.
extern fn did_update_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
extern fn did_update_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity: id) {
let activity = UserActivity::with_inner(activity);
app::<T>(this).updated_user_activity(activity);
}
/// Fires when the application receives a `application:didRegisterForRemoteNotificationsWithDeviceToken:` message.
@ -179,26 +202,13 @@ extern fn accepted_cloudkit_share<T: AppController>(_this: &Object, _: Sel, _: i
/// Fires when the application receives an `application:openURLs` message.
extern fn open_urls<T: AppController>(this: &Object, _: Sel, _: id, file_urls: id) {
app::<T>(this).open_urls(unsafe {
let count: usize = msg_send![file_urls, count];
let mut urls: Vec<Url> = Vec::with_capacity(count);
let urls = map_nsarray(file_urls, |url| unsafe {
let absolute_string = msg_send![url, absoluteString];
let uri = str_from(absolute_string);
Url::parse(uri)
}).into_iter().filter_map(|url| url.ok()).collect();
let mut index = 0;
loop {
let url: id = msg_send![file_urls, objectAtIndex:index];
let absolute_string: id = msg_send![url, absoluteString];
let uri = str_from(absolute_string);
if let Ok(u) = Url::parse(uri) {
urls.push(u);
}
index += 1;
if index == count { break; }
}
urls
});
app::<T>(this).open_urls(urls);
}
/// Fires when the application receives an `application:openFileWithoutUI:` message.
@ -250,13 +260,24 @@ extern fn print_file<T: AppController>(this: &Object, _: Sel, _: id, file: id) -
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
/// message.
extern fn print_files<T: AppController>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
app::<T>(this).print_files(vec![], PrintSettings::default(), match show_print_panels {
let files = map_nsarray(files, |file| {
str_from(file).to_string()
});
let settings = PrintSettings::with_inner(settings);
app::<T>(this).print_files(files, settings, match show_print_panels {
YES => true,
NO => false,
_ => { unreachable!(); }
}).into()
}
/// Called when the application's occlusion state has changed.
extern fn did_change_occlusion_state<T: AppController>(this: &Object, _: Sel, _: id) {
app::<T>(this).occlusion_state_changed();
}
/// Called when the application receives an `application:delegateHandlesKey:` message.
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
/// matter.
@ -314,6 +335,7 @@ pub(crate) fn register_app_controller_class<T: AppController>() -> *const Class
// Managing the Screen
decl.add_method(sel!(applicationDidChangeScreenParameters:), did_change_screen_parameters::<T> as extern fn(&Object, _, _));
decl.add_method(sel!(applicationDidChangeOcclusionState:), did_change_occlusion_state::<T> as extern fn(&Object, _, _));
// User Activities
decl.add_method(sel!(application:willContinueUserActivityWithType:), will_continue_user_activity_with_type::<T> as extern fn(&Object, _, _, id) -> BOOL);

View file

@ -10,7 +10,7 @@ use objc::{class, msg_send, sel, sel_impl};
mod class;
pub mod traits;
pub mod types;
pub mod enums;
use crate::constants::APP_PTR;
use crate::menu::Menu;

View file

@ -3,13 +3,13 @@
use url::Url;
use crate::app::types::{TerminateResponse, PrintResponse};
use crate::app::enums::{TerminateResponse, PrintResponse};
use crate::error::AppKitError;
use crate::menu::Menu;
use crate::printing::types::PrintSettings;
use crate::printing::settings::PrintSettings;
use crate::user_activity::UserActivity;
pub struct CKShareMetaData;
pub struct UserActivity;
/// Controllers interested in processing messages can implement this to respond to messages as
/// they're dispatched. All messages come in on the main thread.
@ -109,14 +109,16 @@ pub trait AppController {
/// Fired when the user is going to continue an activity.
fn will_continue_user_activity(&self, _activity_type: &str) -> bool { false }
/// Fired when data for continuing an activity is available.
fn continue_user_activity(&self, _activity: UserActivity) -> bool { false }
/// Fired when data for continuing an activity is available. Currently, the
/// `restoration_handler` is not used, but there to communicate intent with what this API will
/// eventually be doing.
fn continue_user_activity<F: Fn()>(&self, _activity: UserActivity, _restoration_handler: F) -> bool { false }
/// Fired when the activity could not be continued.
fn did_fail_to_continue_user_activity(&self, _activity: UserActivity, _error: AppKitError) {}
fn failed_to_continue_user_activity(&self, _activity_type: &str, _error: AppKitError) {}
/// Fired after the user activity object has been updated.
fn did_update_user_activity(&self, _activity: UserActivity) {}
fn updated_user_activity(&self, _activity: UserActivity) {}
/// Fires after the user accepted a CloudKit sharing invitation associated with your
/// application.

View file

@ -26,7 +26,7 @@ impl AppKitError {
/// Given an `NSError` (i.e, an id reference) we'll pull out the relevant information and
/// configure this. We pull out the information as it makes the error thread safe this way,
/// which is... easier, in some cases.
pub fn new(error: id) -> Box<Self> {
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];
@ -35,11 +35,15 @@ impl AppKitError {
(code, domain, description)
};
Box::new(AppKitError {
AppKitError {
code: code,
domain: str_from(domain).to_string(),
description: str_from(description).to_string()
})
}
}
pub fn boxed(error: id) -> Box<Self> {
Box::new(AppKitError::new(error))
}
/// Used for cases where we need to return an `NSError` back to the system (e.g, top-level

View file

@ -83,7 +83,7 @@ impl FileManager {
let error: id = nil;
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];
if result == NO {
return Err(AppKitError::new(error));
return Err(AppKitError::new(error).into());
}
}

View file

@ -38,6 +38,7 @@ pub mod notifications;
pub mod pasteboard;
pub mod printing;
pub mod toolbar;
pub mod user_activity;
pub mod utils;
pub mod view;
pub mod webview;

View file

@ -1,5 +1,5 @@
//! Implements types used for printing (both configuring print jobs, as well as the act of printing
//! itself).
pub mod types;
pub use types::*;
pub mod settings;
pub use settings::PrintSettings;

View file

@ -0,0 +1,22 @@
//! 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;
/// `PrintSettings` represents options used in printing, typically passed to you by the
/// application/user.
#[derive(Clone, Debug)]
pub struct PrintSettings {
pub inner: ShareId<Object>
}
impl PrintSettings {
/// Internal method, constructs a wrapper around the backing `NSDictionary` print settings.
pub(crate) fn with_inner(inner: id) -> Self {
PrintSettings {
inner: unsafe { ShareId::from_ptr(inner) }
}
}
}

View file

@ -1,4 +0,0 @@
//! Represents settings for printing items.
#[derive(Copy, Clone, Debug, Default)]
pub struct PrintSettings;

View file

@ -0,0 +1,20 @@
//! A module wrapping `NSUserActivity`.
use cocoa::base::id;
use objc::runtime::Object;
use objc_id::ShareId;
/// Represents an `NSUserActivity`, which acts as a lightweight method to capture the state of your
/// app.
pub struct UserActivity {
pub inner: ShareId<Object>
}
impl UserActivity {
/// An internal method for wrapping a system-provided activity.
pub(crate) fn with_inner(object: id) -> Self {
UserActivity {
inner: unsafe { ShareId::from_ptr(object) }
}
}
}

View file

@ -27,6 +27,28 @@ pub fn str_from(nsstring: id) -> &'static str {
}
}
/// 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<T, F>(array: id, transform: F) -> Vec<T>
where F: Fn(id) -> T {
let count: usize = unsafe { msg_send![array, count] };
let mut ret: Vec<T> = 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.