Further work on finishing up AppController interface
This commit is contained in:
parent
f1689d7cf9
commit
7b5ed88bb1
|
@ -2,8 +2,11 @@
|
||||||
//! creates a custom `NSApplication` subclass that currently does nothing; this is meant as a hook
|
//! creates a custom `NSApplication` subclass that currently does nothing; this is meant as a hook
|
||||||
//! for potential future use.
|
//! for potential future use.
|
||||||
|
|
||||||
use std::unreachable;
|
use std::ffi::c_void;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
use std::unreachable;
|
||||||
|
|
||||||
|
use block::Block;
|
||||||
|
|
||||||
use cocoa::base::{id, nil, BOOL, YES, NO};
|
use cocoa::base::{id, nil, BOOL, YES, NO};
|
||||||
use cocoa::foundation::{NSUInteger};
|
use cocoa::foundation::{NSUInteger};
|
||||||
|
@ -18,9 +21,11 @@ use crate::app::traits::AppController;
|
||||||
use crate::constants::APP_PTR;
|
use crate::constants::APP_PTR;
|
||||||
use crate::error::AppKitError;
|
use crate::error::AppKitError;
|
||||||
use crate::printing::PrintSettings;
|
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 {
|
fn app<T: AppController>(this: &Object) -> &T {
|
||||||
unsafe {
|
unsafe {
|
||||||
let app_ptr: usize = *this.get_ivar(APP_PTR);
|
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.
|
/// Fires when the application delegate receives a `applicationDockMenu:` request.
|
||||||
extern fn dock_menu<T: AppController>(_this: &Object, _: Sel, _: id) -> id {
|
extern fn dock_menu<T: AppController>(this: &Object, _: Sel, _: id) -> id {
|
||||||
nil
|
// @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.
|
/// Fires when the application delegate receives a `application:willPresentError:` notification.
|
||||||
extern fn will_present_error<T: AppController>(this: &Object, _: Sel, _: id, error: id) -> id {
|
extern fn will_present_error<T: AppController>(this: &Object, _: Sel, _: id, error: id) -> id {
|
||||||
let error = AppKitError::new(error);
|
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.
|
/// 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:`
|
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
|
||||||
/// notification.
|
/// 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);
|
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,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification.
|
/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification.
|
||||||
extern fn continue_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, _: id, _: id) -> BOOL {
|
extern fn continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL {
|
||||||
NO
|
// @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
|
/// Fires when the application receives a
|
||||||
/// `application:didFailToContinueUserActivityWithType:error:` message.
|
/// `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.
|
/// 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.
|
/// 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.
|
/// Fires when the application receives an `application:openURLs` message.
|
||||||
extern fn open_urls<T: AppController>(this: &Object, _: Sel, _: id, file_urls: id) {
|
extern fn open_urls<T: AppController>(this: &Object, _: Sel, _: id, file_urls: id) {
|
||||||
app::<T>(this).open_urls(unsafe {
|
let urls = map_nsarray(file_urls, |url| unsafe {
|
||||||
let count: usize = msg_send![file_urls, count];
|
let absolute_string = msg_send![url, absoluteString];
|
||||||
let mut urls: Vec<Url> = Vec::with_capacity(count);
|
let uri = str_from(absolute_string);
|
||||||
|
Url::parse(uri)
|
||||||
|
}).into_iter().filter_map(|url| url.ok()).collect();
|
||||||
|
|
||||||
let mut index = 0;
|
app::<T>(this).open_urls(urls);
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives an `application:openFileWithoutUI:` message.
|
/// 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:`
|
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
|
||||||
/// message.
|
/// message.
|
||||||
extern fn print_files<T: AppController>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
|
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,
|
YES => true,
|
||||||
NO => false,
|
NO => false,
|
||||||
_ => { unreachable!(); }
|
_ => { unreachable!(); }
|
||||||
}).into()
|
}).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.
|
/// Called when the application receives an `application:delegateHandlesKey:` message.
|
||||||
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
|
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
|
||||||
/// matter.
|
/// matter.
|
||||||
|
@ -314,6 +335,7 @@ pub(crate) fn register_app_controller_class<T: AppController>() -> *const Class
|
||||||
|
|
||||||
// Managing the Screen
|
// Managing the Screen
|
||||||
decl.add_method(sel!(applicationDidChangeScreenParameters:), did_change_screen_parameters::<T> as extern fn(&Object, _, _));
|
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
|
// User Activities
|
||||||
decl.add_method(sel!(application:willContinueUserActivityWithType:), will_continue_user_activity_with_type::<T> as extern fn(&Object, _, _, id) -> BOOL);
|
decl.add_method(sel!(application:willContinueUserActivityWithType:), will_continue_user_activity_with_type::<T> as extern fn(&Object, _, _, id) -> BOOL);
|
||||||
|
|
|
@ -10,7 +10,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
mod class;
|
mod class;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub mod types;
|
pub mod enums;
|
||||||
|
|
||||||
use crate::constants::APP_PTR;
|
use crate::constants::APP_PTR;
|
||||||
use crate::menu::Menu;
|
use crate::menu::Menu;
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::app::types::{TerminateResponse, PrintResponse};
|
use crate::app::enums::{TerminateResponse, PrintResponse};
|
||||||
use crate::error::AppKitError;
|
use crate::error::AppKitError;
|
||||||
use crate::menu::Menu;
|
use crate::menu::Menu;
|
||||||
use crate::printing::types::PrintSettings;
|
use crate::printing::settings::PrintSettings;
|
||||||
|
use crate::user_activity::UserActivity;
|
||||||
|
|
||||||
pub struct CKShareMetaData;
|
pub struct CKShareMetaData;
|
||||||
pub struct UserActivity;
|
|
||||||
|
|
||||||
/// Controllers interested in processing messages can implement this to respond to messages as
|
/// Controllers interested in processing messages can implement this to respond to messages as
|
||||||
/// they're dispatched. All messages come in on the main thread.
|
/// 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.
|
/// Fired when the user is going to continue an activity.
|
||||||
fn will_continue_user_activity(&self, _activity_type: &str) -> bool { false }
|
fn will_continue_user_activity(&self, _activity_type: &str) -> bool { false }
|
||||||
|
|
||||||
/// Fired when data for continuing an activity is available.
|
/// Fired when data for continuing an activity is available. Currently, the
|
||||||
fn continue_user_activity(&self, _activity: UserActivity) -> bool { false }
|
/// `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.
|
/// 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.
|
/// 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
|
/// Fires after the user accepted a CloudKit sharing invitation associated with your
|
||||||
/// application.
|
/// application.
|
||||||
|
|
|
@ -26,7 +26,7 @@ impl AppKitError {
|
||||||
/// Given an `NSError` (i.e, an id reference) we'll pull out the relevant information and
|
/// 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,
|
/// configure this. We pull out the information as it makes the error thread safe this way,
|
||||||
/// which is... easier, in some cases.
|
/// 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, domain, description) = unsafe {
|
||||||
let code: usize = msg_send![error, code];
|
let code: usize = msg_send![error, code];
|
||||||
let domain: id = msg_send![error, domain];
|
let domain: id = msg_send![error, domain];
|
||||||
|
@ -35,11 +35,15 @@ impl AppKitError {
|
||||||
(code, domain, description)
|
(code, domain, description)
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(AppKitError {
|
AppKitError {
|
||||||
code: code,
|
code: code,
|
||||||
domain: str_from(domain).to_string(),
|
domain: str_from(domain).to_string(),
|
||||||
description: str_from(description).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
|
/// Used for cases where we need to return an `NSError` back to the system (e.g, top-level
|
||||||
|
|
|
@ -83,7 +83,7 @@ impl FileManager {
|
||||||
let error: id = nil;
|
let error: id = nil;
|
||||||
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];
|
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];
|
||||||
if result == NO {
|
if result == NO {
|
||||||
return Err(AppKitError::new(error));
|
return Err(AppKitError::new(error).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub mod notifications;
|
||||||
pub mod pasteboard;
|
pub mod pasteboard;
|
||||||
pub mod printing;
|
pub mod printing;
|
||||||
pub mod toolbar;
|
pub mod toolbar;
|
||||||
|
pub mod user_activity;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
pub mod webview;
|
pub mod webview;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! Implements types used for printing (both configuring print jobs, as well as the act of printing
|
//! Implements types used for printing (both configuring print jobs, as well as the act of printing
|
||||||
//! itself).
|
//! itself).
|
||||||
|
|
||||||
pub mod types;
|
pub mod settings;
|
||||||
pub use types::*;
|
pub use settings::PrintSettings;
|
||||||
|
|
22
appkit/src/printing/settings.rs
Normal file
22
appkit/src/printing/settings.rs
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
//! Represents settings for printing items.
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
pub struct PrintSettings;
|
|
20
appkit/src/user_activity.rs
Normal file
20
appkit/src/user_activity.rs
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
/// 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
|
/// 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.
|
/// `Rc::into_raw()`) otherwise you'll cause problems due to the `Drop` logic.
|
||||||
|
|
Loading…
Reference in a new issue