From f1689d7cf9b4ddcaae203ad29477bc2b2cc3ba3f Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Sun, 15 Mar 2020 19:53:09 -0700 Subject: [PATCH] Nearing 100% coverage for API. This brings in some necessary cleanup now that we've found the right approach to all of this. --- appkit/src/app/class.rs | 371 +++++++++++++++++++++++++++++++++++ appkit/src/app/events.rs | 27 --- appkit/src/app/mod.rs | 73 +------ appkit/src/app/traits.rs | 195 ++++++++++++++++++ appkit/src/app/types.rs | 60 ++++++ appkit/src/error.rs | 16 +- appkit/src/lib.rs | 3 +- appkit/src/printing/mod.rs | 5 + appkit/src/printing/types.rs | 4 + 9 files changed, 661 insertions(+), 93 deletions(-) create mode 100644 appkit/src/app/class.rs delete mode 100644 appkit/src/app/events.rs create mode 100644 appkit/src/app/traits.rs create mode 100644 appkit/src/app/types.rs create mode 100644 appkit/src/printing/mod.rs create mode 100644 appkit/src/printing/types.rs diff --git a/appkit/src/app/class.rs b/appkit/src/app/class.rs new file mode 100644 index 0000000..d3cf06d --- /dev/null +++ b/appkit/src/app/class.rs @@ -0,0 +1,371 @@ +//! This module implements forwarding methods for standard `NSApplicationDelegate` calls. It also +//! creates a custom `NSApplication` subclass that currently does nothing; this is meant as a hook +//! for potential future use. + +use std::unreachable; +use std::sync::Once; + +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::app::traits::AppController; +use crate::constants::APP_PTR; +use crate::error::AppKitError; +use crate::printing::PrintSettings; +use crate::utils::str_from; + +/// A handy method for grabbing our `AppController` from the pointer. +fn app(this: &Object) -> &T { + unsafe { + let app_ptr: usize = *this.get_ivar(APP_PTR); + let app = app_ptr as *const T; + &*app + } +} + +/// Fires when the Application Delegate receives a `applicationWillFinishLaunching` notification. +extern fn will_finish_launching(this: &Object, _: Sel, _: id) { + app::(this).will_finish_launching(); +} + +/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification. +extern fn did_finish_launching(this: &Object, _: Sel, _: id) { + app::(this).did_finish_launching(); +} + +/// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification. +extern fn will_become_active(this: &Object, _: Sel, _: id) { + app::(this).will_become_active(); +} + +/// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification. +extern fn did_become_active(this: &Object, _: Sel, _: id) { + app::(this).did_become_active(); +} + +/// Fires when the Application Delegate receives a `applicationWillResignActive` notification. +extern fn will_resign_active(this: &Object, _: Sel, _: id) { + app::(this).will_resign_active(); +} + +/// Fires when the Application Delegate receives a `applicationDidResignActive` notification. +extern fn did_resign_active(this: &Object, _: Sel, _: id) { + app::(this).did_resign_active(); +} + +/// Fires when the Application Delegate receives a 'applicationShouldTerminate:` notification. +extern fn should_terminate(this: &Object, _: Sel, _: id) -> NSUInteger { + app::(this).should_terminate().into() +} + +/// Fires when the Application Delegate receives a `applicationWillTerminate:` notification. +extern fn will_terminate(this: &Object, _: Sel, _: id) { + app::(this).will_terminate(); +} + +/// Fires when the Application Delegate receives a `applicationWillHide:` notification. +extern fn will_hide(this: &Object, _: Sel, _: id) { + app::(this).will_hide(); +} + +/// Fires when the Application Delegate receives a `applicationDidHide:` notification. +extern fn did_hide(this: &Object, _: Sel, _: id) { + app::(this).did_hide(); +} + +/// Fires when the Application Delegate receives a `applicationWillUnhide:` notification. +extern fn will_unhide(this: &Object, _: Sel, _: id) { + app::(this).will_unhide(); +} + +/// Fires when the Application Delegate receives a `applicationDidUnhide:` notification. +extern fn did_unhide(this: &Object, _: Sel, _: id) { + app::(this).did_unhide(); +} + +/// Fires when the Application Delegate receives a `applicationWillUpdate:` notification. +extern fn will_update(this: &Object, _: Sel, _: id) { + app::(this).will_update(); +} + +/// Fires when the Application Delegate receives a `applicationDidUpdate:` notification. +extern fn did_update(this: &Object, _: Sel, _: id) { + app::(this).did_update(); +} + +/// Fires when the Application Delegate receives a +/// `applicationShouldHandleReopen:hasVisibleWindows:` notification. +extern fn should_handle_reopen(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL { + match app::(this).should_handle_reopen(match has_visible_windows { + YES => true, + NO => false, + _ => { unreachable!(); }, + }) { + true => YES, + false => NO + } +} + +/// Fires when the application delegate receives a `applicationDockMenu:` request. +extern fn dock_menu(_this: &Object, _: Sel, _: id) -> id { + nil +} + +/// Fires when the application delegate receives a `application:willPresentError:` notification. +extern fn will_present_error(this: &Object, _: Sel, _: id, error: id) -> id { + let error = AppKitError::new(error); + app::(this).will_present_error(*error).into_nserror() +} + +/// Fires when the application receives a `applicationDidChangeScreenParameters:` notification. +extern fn did_change_screen_parameters(this: &Object, _: Sel, _: id) { + app::(this).did_change_screen_parameters(); +} + +/// 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); + NO + + /*match app::(this).will_continue_user_activity_with_type(activity) { + true => YES, + false => NO + }*/ +} + +/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification. +extern fn continue_user_activity(_this: &Object, _: Sel, _: id, _: id, _: id) -> BOOL { + NO +} + +/// Fires when the application receives a +/// `application:didFailToContinueUserActivityWithType:error:` message. +extern fn failed_to_continue_user_activity(_this: &Object, _: Sel, _: id, activity_type: id, error: id) { + +} + +/// Fires when the application receives a `application:didUpdateUserActivity:` message. +extern fn did_update_user_activity(_this: &Object, _: Sel, _: id, _: id) { + +} + +/// Fires when the application receives a `application:didRegisterForRemoteNotificationsWithDeviceToken:` message. +extern fn registered_for_remote_notifications(_this: &Object, _: Sel, _: id, _: id) { + +} + +/// Fires when the application receives a `application:didFailToRegisterForRemoteNotificationsWithError:` message. +extern fn failed_to_register_for_remote_notifications(_this: &Object, _: Sel, _: id, _: id) { + +} + +/// Fires when the application receives a `application:didReceiveRemoteNotification:` message. +extern fn did_receive_remote_notification(_this: &Object, _: Sel, _: id, _: id) { + +} + +/// Fires when the application receives a `application:userDidAcceptCloudKitShareWithMetadata:` +/// message. +extern fn accepted_cloudkit_share(_this: &Object, _: Sel, _: id, _: id) { + +} + +/// Fires when the application receives an `application:openURLs` message. +extern fn open_urls(this: &Object, _: Sel, _: id, file_urls: id) { + app::(this).open_urls(unsafe { + let count: usize = msg_send![file_urls, count]; + let mut urls: Vec = Vec::with_capacity(count); + + 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 + }); +} + +/// 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); + + match app::(this).open_file_without_ui(filename) { + true => YES, + false => NO + } +} + +/// Fired when the application receives an `applicationShouldOpenUntitledFile:` message. +extern fn should_open_untitled_file(this: &Object, _: Sel, _: id) -> BOOL { + match app::(this).should_open_untitled_file() { + true => YES, + false => NO + } +} + +/// Fired when the application receives an `applicationOpenUntitledFile:` message. +extern fn open_untitled_file(this: &Object, _: Sel, _: id) -> BOOL { + match app::(this).open_untitled_file() { + true => YES, + false => NO + } +} + +/// 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); + + match app::(this).open_temp_file(filename) { + true => YES, + false => NO + } +} + +/// 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); + + match app::(this).print_file(filename) { + true => YES, + false => NO + } +} + +/// 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 { + app::(this).print_files(vec![], PrintSettings::default(), match show_print_panels { + YES => true, + NO => false, + _ => { unreachable!(); } + }).into() +} + +/// 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. +extern fn delegate_handles_key(this: &Object, _: Sel, _: id, key: id) -> BOOL { + let key = str_from(key); + + match app::(this).delegate_handles_key(key) { + true => YES, + false => NO + } +} + +/// Registers an `NSObject` application delegate, and configures it for the various callbacks and +/// pointers we need to have. +pub(crate) fn register_app_controller_class() -> *const Class { + static mut DELEGATE_CLASS: *const Class = 0 as *const Class; + static INIT: Once = Once::new(); + + INIT.call_once(|| unsafe { + let superclass = class!(NSObject); + let mut decl = ClassDecl::new("RSTAppController", superclass).unwrap(); + + decl.add_ivar::(APP_PTR); + + // Launching Applications + decl.add_method(sel!(applicationWillFinishLaunching:), will_finish_launching:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching:: as extern fn(&Object, _, _)); + + // Managing Active Status + decl.add_method(sel!(applicationWillBecomeActive:), will_become_active:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationDidBecomeActive:), did_become_active:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationWillResignActive:), will_resign_active:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationDidResignActive:), did_resign_active:: as extern fn(&Object, _, _)); + + // Terminating Applications + decl.add_method(sel!(applicationShouldTerminate:), should_terminate:: as extern fn(&Object, _, _) -> NSUInteger); + decl.add_method(sel!(applicationWillTerminate:), will_terminate:: as extern fn(&Object, _, _)); + + // Hiding Applications + decl.add_method(sel!(applicationWillHide:), will_hide:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationDidHide:), did_hide:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationWillUnhide:), will_unhide:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationDidUnhide:), did_unhide:: as extern fn(&Object, _, _)); + + // Managing Windows + decl.add_method(sel!(applicationWillUpdate:), will_update:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationDidUpdate:), did_update:: as extern fn(&Object, _, _)); + decl.add_method(sel!(applicationShouldHandleReopen:hasVisibleWindows:), should_handle_reopen:: as extern fn(&Object, _, _, BOOL) -> BOOL); + + // Dock Menu + decl.add_method(sel!(applicationDockMenu:), dock_menu:: as extern fn(&Object, _, _) -> id); + + // Displaying Errors + decl.add_method(sel!(application:willPresentError:), will_present_error:: as extern fn(&Object, _, _, id) -> id); + + // Managing the Screen + decl.add_method(sel!(applicationDidChangeScreenParameters:), did_change_screen_parameters:: as extern fn(&Object, _, _)); + + // User Activities + decl.add_method(sel!(application:willContinueUserActivityWithType:), will_continue_user_activity_with_type:: as extern fn(&Object, _, _, id) -> BOOL); + decl.add_method(sel!(application:continueUserActivity:restorationHandler:), continue_user_activity:: as extern fn(&Object, _, _, id, id) -> BOOL); + decl.add_method(sel!(application:didFailToContinueUserActivityWithType:error:), failed_to_continue_user_activity:: as extern fn(&Object, _, _, id, id)); + decl.add_method(sel!(application:didUpdateUserActivity:), did_update_user_activity:: as extern fn(&Object, _, _, id)); + + // Handling push notifications + decl.add_method(sel!(application:didRegisterForRemoteNotificationsWithDeviceToken:), registered_for_remote_notifications:: as extern fn(&Object, _, _, id)); + decl.add_method(sel!(application:didFailToRegisterForRemoteNotificationsWithError:), failed_to_register_for_remote_notifications:: as extern fn(&Object, _, _, id)); + decl.add_method(sel!(application:didReceiveRemoteNotification:), did_receive_remote_notification:: as extern fn(&Object, _, _, id)); + + // CloudKit + decl.add_method(sel!(application:userDidAcceptCloudKitShareWithMetadata:), accepted_cloudkit_share:: as extern fn(&Object, _, _, id)); + + // Opening Files + decl.add_method(sel!(application:openURLs:), open_urls:: as extern fn(&Object, _, _, id)); + decl.add_method(sel!(application:openFileWithoutUI:), open_file_without_ui:: as extern fn(&Object, _, _, id) -> BOOL); + decl.add_method(sel!(applicationShouldOpenUntitledFile:), should_open_untitled_file:: as extern fn(&Object, _, _) -> BOOL); + decl.add_method(sel!(applicationOpenUntitledFile:), open_untitled_file:: as extern fn(&Object, _, _) -> BOOL); + decl.add_method(sel!(application:openTempFile:), open_temp_file:: as extern fn(&Object, _, _, id) -> BOOL); + + // Printing + decl.add_method(sel!(application:printFile:), print_file:: as extern fn(&Object, _, _, id) -> BOOL); + decl.add_method(sel!(application:printFiles:withSettings:showPrintPanels:), print_files:: as extern fn(&Object, _, id, id, id, BOOL) -> NSUInteger); + + // @TODO: Restoring Application State + // Depends on NSCoder support, which is... welp. + + // Scripting + decl.add_method(sel!(application:delegateHandlesKey:), delegate_handles_key:: as extern fn(&Object, _, _, id) -> BOOL); + + DELEGATE_CLASS = decl.register(); + }); + + unsafe { + DELEGATE_CLASS + } +} + +/// Used for injecting a custom NSApplication. Currently does nothing. +pub(crate) fn register_app_class() -> *const Class { + static mut APP_CLASS: *const Class = 0 as *const Class; + static INIT: Once = Once::new(); + + INIT.call_once(|| unsafe { + let superclass = class!(NSApplication); + let decl = ClassDecl::new("RSTApplication", superclass).unwrap(); + APP_CLASS = decl.register(); + }); + + unsafe { + APP_CLASS + } +} diff --git a/appkit/src/app/events.rs b/appkit/src/app/events.rs deleted file mode 100644 index 502e8aa..0000000 --- a/appkit/src/app/events.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! This module handles providing a special subclass of `NSApplication`. -//! -//! Now, I know what you're thinking: this is dumb. -//! -//! However, there are rare cases where this is beneficial, and by default we're doing nothing... -//! so consider this a placeholder that we might use in the future for certain things. - -use std::sync::Once; - -use objc::declare::ClassDecl; -use objc::runtime::Class; - -/// Used for injecting a custom NSApplication. Currently does nothing. -pub(crate) fn register_app_class() -> *const Class { - static mut DELEGATE_CLASS: *const Class = 0 as *const Class; - static INIT: Once = Once::new(); - - INIT.call_once(|| unsafe { - let superclass = Class::get("NSApplication").unwrap(); - let decl = ClassDecl::new("RSTApplication", superclass).unwrap(); - DELEGATE_CLASS = decl.register(); - }); - - unsafe { - DELEGATE_CLASS - } -} diff --git a/appkit/src/app/mod.rs b/appkit/src/app/mod.rs index c085fe1..c00262c 100644 --- a/appkit/src/app/mod.rs +++ b/appkit/src/app/mod.rs @@ -1,32 +1,21 @@ //! A wrapper for `NSApplicationDelegate` on macOS. Handles looping back events and providing a very janky //! messaging architecture. -use std::sync::Once; - use cocoa::base::{id, nil}; use cocoa::appkit::{NSRunningApplication}; use objc_id::Id; -use objc::declare::ClassDecl; -use objc::runtime::{Class, Object, Sel}; +use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; +mod class; +pub mod traits; +pub mod types; + use crate::constants::APP_PTR; use crate::menu::Menu; - -mod events; -use events::register_app_class; - -pub trait Dispatcher { - type Message: Send + Sync; - - fn on_message(&self, _message: Self::Message) {} -} - -pub trait AppDelegate { - fn did_finish_launching(&self) {} - fn did_become_active(&self) {} -} +use class::{register_app_class, register_app_controller_class}; +pub use traits::{AppController, Dispatcher}; /// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime, /// which is where our application instance lives. It also injects an `NSObject` subclass, @@ -61,7 +50,7 @@ impl App { } } -impl App where M: Send + Sync + 'static, T: AppDelegate + Dispatcher { +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) { @@ -97,7 +86,7 @@ impl App where M: Send + Sync + 'static, T: AppDelegate + Dispatcher let app_delegate = Box::new(delegate); let objc_delegate = unsafe { - let delegate_class = register_delegate_class::(); + let delegate_class = register_app_controller_class::(); let delegate: id = msg_send![delegate_class, new]; let delegate_ptr: *const T = &*app_delegate; (&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize); @@ -114,7 +103,7 @@ impl App where M: Send + Sync + 'static, T: AppDelegate + Dispatcher } /// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called. - /// If you're wondering where to go from here... you need an `AppDelegate` that implements + /// If you're wondering where to go from here... you need an `AppController` that implements /// `did_finish_launching`. :) pub fn run(&self) { unsafe { @@ -125,45 +114,3 @@ impl App where M: Send + Sync + 'static, T: AppDelegate + Dispatcher } } } - -/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification. -extern fn did_finish_launching(this: &Object, _: Sel, _: id) { - unsafe { - let app_ptr: usize = *this.get_ivar(APP_PTR); - let app = app_ptr as *const D; - (*app).did_finish_launching(); - }; -} - -/// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification. -extern fn did_become_active(this: &Object, _: Sel, _: id) { - unsafe { - let app_ptr: usize = *this.get_ivar(APP_PTR); - let app = app_ptr as *const D; - (*app).did_become_active(); - } -} - -/// Registers an `NSObject` application delegate, and configures it for the various callbacks and -/// pointers we need to have. -fn register_delegate_class() -> *const Class { - static mut DELEGATE_CLASS: *const Class = 0 as *const Class; - static INIT: Once = Once::new(); - - INIT.call_once(|| unsafe { - let superclass = Class::get("NSObject").unwrap(); - let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap(); - - decl.add_ivar::(APP_PTR); - - // Add callback methods - decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching:: as extern fn(&Object, _, _)); - decl.add_method(sel!(applicationDidBecomeActive:), did_become_active:: as extern fn(&Object, _, _)); - - DELEGATE_CLASS = decl.register(); - }); - - unsafe { - DELEGATE_CLASS - } -} diff --git a/appkit/src/app/traits.rs b/appkit/src/app/traits.rs new file mode 100644 index 0000000..113c963 --- /dev/null +++ b/appkit/src/app/traits.rs @@ -0,0 +1,195 @@ +//! Traits that an implementing application can conform to. These aim to wrap the general +//! lifecycles across macOS/iOS/etc, while still conforming to a Rust-ish approach. + +use url::Url; + +use crate::app::types::{TerminateResponse, PrintResponse}; +use crate::error::AppKitError; +use crate::menu::Menu; +use crate::printing::types::PrintSettings; + +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. +pub trait Dispatcher { + type Message: Send + Sync; + + fn on_message(&self, _message: Self::Message) {} +} + +/// `AppController` is more or less `AppDelegate` from the Objective-C/Swift side, just named +/// differently to fit in with the general naming scheme found within this framework. You can +/// implement methods from this trait in order to respond to lifecycle events that the system will +/// fire off. +pub trait AppController { + /// Called right before the application will finish launching. You really, probably, want to do + /// your setup in `did_finish_launching` unless you're sure of what you're doing. + fn will_finish_launching(&self) {} + + /// Fired when the application has finished launching. Unlike most other "load" lifecycle + /// events in this framework, you don't get a reference to an app here - if you need to call + /// through to your shared application, then used the `App::shared()` call. + fn did_finish_launching(&self) {} + + /// Fired immediately before the application is about to become active. + fn will_become_active(&self) {} + + /// Fired when the application is about to become active. + fn did_become_active(&self) {} + + /// Fired when the application is about to resign active state. + fn will_resign_active(&self) {} + + /// Fired when the application has resigned active state. + fn did_resign_active(&self) {} + + /// Fired when the application is about to hide. + fn will_hide(&self) {} + + /// Fired after the application has hidden. + fn did_hide(&self) {} + + /// Fired when the application is about to unhide itself. + fn will_unhide(&self) {} + + /// Fired after the application has unhidden itself. + fn did_unhide(&self) {} + + /// Fired immediately before the application object updates its windows. + fn will_update(&self) {} + + /// Fired immediately after the application object updates its windows. + fn did_update(&self) {} + + /// This is fired after the `Quit` menu item has been selected, or after you've called `App::terminate()`. + /// + /// In most cases you just want `TerminateResponse::Now` here, which enables business as usual. If you need, + /// though, you can cancel the termination via `TerminateResponse::Cancel` to continue something essential. If + /// you do this, you'll need to be sure to call `App::reply_to_termination_request()` to circle + /// back. + fn should_terminate(&self) -> TerminateResponse { TerminateResponse::Now } + + /// Fired before the application terminates. You can use this to do any required cleanup. + fn will_terminate(&self) {} + + /// Sent by the application to the delegate prior to default behavior to reopen AppleEvents. + /// + /// `has_visible_windows` indicates whether the Application object found any visible windows in your application. + /// You can use this value as an indication of whether the application would do anything if you return `true`. + /// + /// Return `true` if you want the application to perform its normal tasks, or `false` if you want the + /// application to do nothing. The default implementation of this method returns `true`. + /// + /// Some finer points of discussion, from Apple documentation: + /// + /// These events are sent whenever the Finder reactivates an already running application because someone + /// double-clicked it again or used the dock to activate it. + /// + /// For most document-based applications, an untitled document will be created. + /// + /// [Read more + /// here](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc) + fn should_handle_reopen(&self, _has_visible_windows: bool) -> bool { true } + + /// Supply a dock menu for the application dynamically. The default implementation for this + /// method returns `None`, for no menu. + fn dock_menu(&self) -> Option { None } + + /// Fired before the application presents an error message to the user. If you find the error + /// to be... not what you want, you can take it, alter it, and return it anew. The default + /// implementation of this method simply returns the error as-is. + fn will_present_error(&self, error: AppKitError) -> AppKitError { error } + + /// Fired when the screen parameters for the application have changed (e.g, the user changed + /// something in their settings). + fn did_change_screen_parameters(&self) {} + + /// 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 the activity could not be continued. + fn did_fail_to_continue_user_activity(&self, _activity: UserActivity, _error: AppKitError) {} + + /// Fired after the user activity object has been updated. + fn did_update_user_activity(&self, _activity: UserActivity) {} + + /// Fires after the user accepted a CloudKit sharing invitation associated with your + /// application. + fn user_accepted_cloudkit_share(&self, _share_metadata: CKShareMetaData) {} + + /// Fired when you have a list of `Url`'s to open. This is best explained by quoting the Apple + /// documentation verbatim: + /// + /// _"AppKit calls this method when your app is asked to open one or more URL-based resources. + /// You must declare the URL types that your app supports in your `Info.plist` file using the `CFBundleURLTypes` key. + /// The list can also include URLs for documents for which your app does not have an associated `NSDocument` class. + /// You configure document types by adding the `CFBundleDocumentTypes` key to your Info.plist + /// file." + /// + /// Note that since we have this as the de-facto method of handling resource opens, the system + /// will _not_ call `application:openFile:` or `application:openFiles`. + fn open_urls(&self, _urls: Vec) { } + + /// Fired when the file is requested to be opened programmatically. This is not a commonly used + /// or implemented method. + /// + /// According to Apple: + /// + /// _"The method should open the file without bringing up its application’s user interface—that is, + /// work with the file is under programmatic control of sender, rather than under keyboard control of the user."_ + /// + /// It's unclear how supported this is in sandbox environments, so use at your own risk. + fn open_file_without_ui(&self, _filename: &str) -> bool { false } + + /// Fired when the application is ready and able to open a temporary file. + /// Return `true` or `false` here depending on whether the operation was successful. + /// + /// It's your responsibility to remove the temp file. + fn open_temp_file(&self, _filename: &str) -> bool { false } + + /// Fired before attempting to open an untitled file. Return `true` here if you want + /// `open_untitled_file` to be called by the system. + fn should_open_untitled_file(&self) -> bool { false } + + /// Called when the application has asked you to open a new, untitled file. + /// Returns a `bool` indicating whether the file was successfully opened or not. + fn open_untitled_file(&self) -> bool { true } + + /// Sent when the user starts up the application on the command line with the -NSPrint option. + /// The application terminates immediately after this method returns. For more information, + /// cosnult the official Apple documentation. + /// + /// (You probably never need to implement this, but we support it anyway) + fn print_file(&self, _filename: &str) -> bool { false } + + /// Called when the user has requested to print some files. + /// + /// Returns a `PrintResponse`, indicating status of the print job. You can return + /// `PrintResponse::ReplyLater` if you need to do something like confirming via a popover. If + /// you do this, though, you must call `App::reply_to_open_or_print()` when the operation has + /// been completed. + /// + /// Note that macOS has a long-deprecated `printFiles:` method, which your searching may bring + /// up. This method really maps to `application:printFiles:withSettings:showPrintPanels:`, so + /// be sure to just... look there. + fn print_files(&self, _filenames: Vec, _settings: PrintSettings, _show_panels: bool) -> PrintResponse { + PrintResponse::Failure + } + + /// Fired when the occlusion state for the app has changed. + /// + /// From Apple's docs, as there's no other way to describe this better: _upon receiving this method, you can query the + /// application for its occlusion state. Note that this only notifies about changes in the state of the occlusion, not + /// when the occlusion region changes. You can use this method to increase responsiveness and save power by halting any + /// expensive calculations that the user can not see._ + fn occlusion_state_changed(&self) {} + + /// Fired when the system wants to know whether your application, via scripting, can handle the + /// key specifying operations. + fn delegate_handles_key(&self, _key: &str) -> bool { false } +} diff --git a/appkit/src/app/types.rs b/appkit/src/app/types.rs new file mode 100644 index 0000000..ccdd23f --- /dev/null +++ b/appkit/src/app/types.rs @@ -0,0 +1,60 @@ +//! Various types used at the AppController level. + +use cocoa::foundation::NSUInteger; + +/// Used for determining how an application should handle quitting/terminating. +/// You return this in your `AppController` `should_terminate` method. +#[derive(Copy, Clone, Debug)] +pub enum TerminateResponse { + /// Proceed with termination. + Now, + + /// App should not be terminated. + Cancel, + + /// It might be fine to proceed with termination later. Returning this value causes + /// Cocoa to run the run loop until `should_terminate()` returns `true` or `false`. + /// + /// This return value is for primarily for cases where you need to provide alerts + /// in order to decide whether to quit. + Later +} + +impl From for NSUInteger { + fn from(response: TerminateResponse) -> NSUInteger { + match response { + TerminateResponse::Now => 1, + TerminateResponse::Cancel => 0, + TerminateResponse::Later => 2 + } + } +} + +/// 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/error.rs b/appkit/src/error.rs index d895931..1499fd0 100644 --- a/appkit/src/error.rs +++ b/appkit/src/error.rs @@ -6,8 +6,9 @@ use std::error; use std::fmt; -use cocoa::base::id; -use objc::{msg_send, sel, sel_impl}; +use cocoa::base::{id, nil}; +use cocoa::foundation::{NSInteger, NSString}; +use objc::{class, msg_send, sel, sel_impl}; use crate::utils::str_from; @@ -40,6 +41,17 @@ impl AppKitError { description: str_from(description).to_string() }) } + + /// Used for cases where we need to return an `NSError` back to the system (e.g, top-level + /// error handling). We just create a new `NSError` so the `AppKitError` crate can be mostly + /// thread safe. + pub fn into_nserror(self) -> id { + unsafe { + let domain = NSString::alloc(nil).init_str(&self.domain); + let code = self.code as NSInteger; + msg_send![class!(NSError), errorWithDomain:domain code:code userInfo:nil] + } + } } impl fmt::Display for AppKitError { diff --git a/appkit/src/lib.rs b/appkit/src/lib.rs index b6982bf..9868fd1 100644 --- a/appkit/src/lib.rs +++ b/appkit/src/lib.rs @@ -36,6 +36,7 @@ pub mod menu; pub mod networking; pub mod notifications; pub mod pasteboard; +pub mod printing; pub mod toolbar; pub mod utils; pub mod view; @@ -46,7 +47,7 @@ pub mod window; pub use url; pub mod prelude { - pub use crate::app::{App, AppDelegate, Dispatcher}; + pub use crate::app::{App, AppController, Dispatcher}; pub use crate::layout::LayoutConstraint; diff --git a/appkit/src/printing/mod.rs b/appkit/src/printing/mod.rs new file mode 100644 index 0000000..2437892 --- /dev/null +++ b/appkit/src/printing/mod.rs @@ -0,0 +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::*; diff --git a/appkit/src/printing/types.rs b/appkit/src/printing/types.rs new file mode 100644 index 0000000..1c13b95 --- /dev/null +++ b/appkit/src/printing/types.rs @@ -0,0 +1,4 @@ +//! Represents settings for printing items. + +#[derive(Copy, Clone, Debug, Default)] +pub struct PrintSettings;