Nearing 100% coverage for API. This brings in some necessary cleanup now that we've found the right approach to all of this.
This commit is contained in:
parent
3a89d8e5f5
commit
f1689d7cf9
371
appkit/src/app/class.rs
Normal file
371
appkit/src/app/class.rs
Normal file
|
@ -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<T: AppController>(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<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).will_finish_launching();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
|
||||||
|
extern fn did_finish_launching<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).did_finish_launching();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification.
|
||||||
|
extern fn will_become_active<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).will_become_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification.
|
||||||
|
extern fn did_become_active<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).did_become_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationWillResignActive` notification.
|
||||||
|
extern fn will_resign_active<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).will_resign_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationDidResignActive` notification.
|
||||||
|
extern fn did_resign_active<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).did_resign_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a 'applicationShouldTerminate:` notification.
|
||||||
|
extern fn should_terminate<T: AppController>(this: &Object, _: Sel, _: id) -> NSUInteger {
|
||||||
|
app::<T>(this).should_terminate().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationWillTerminate:` notification.
|
||||||
|
extern fn will_terminate<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).will_terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationWillHide:` notification.
|
||||||
|
extern fn will_hide<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).will_hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationDidHide:` notification.
|
||||||
|
extern fn did_hide<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).did_hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationWillUnhide:` notification.
|
||||||
|
extern fn will_unhide<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).will_unhide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationDidUnhide:` notification.
|
||||||
|
extern fn did_unhide<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).did_unhide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationWillUpdate:` notification.
|
||||||
|
extern fn will_update<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).will_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a `applicationDidUpdate:` notification.
|
||||||
|
extern fn did_update<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).did_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the Application Delegate receives a
|
||||||
|
/// `applicationShouldHandleReopen:hasVisibleWindows:` notification.
|
||||||
|
extern fn should_handle_reopen<T: AppController>(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL {
|
||||||
|
match app::<T>(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<T: AppController>(_this: &Object, _: Sel, _: id) -> id {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the application receives a `applicationDidChangeScreenParameters:` notification.
|
||||||
|
extern fn did_change_screen_parameters<T: AppController>(this: &Object, _: Sel, _: id) {
|
||||||
|
app::<T>(this).did_change_screen_parameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
let activity = str_from(activity_type);
|
||||||
|
NO
|
||||||
|
|
||||||
|
/*match app::<T>(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<T: AppController>(_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<T: AppController>(_this: &Object, _: Sel, _: id, activity_type: id, error: id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the application receives a `application:didUpdateUserActivity:` message.
|
||||||
|
extern fn did_update_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the application receives a `application:didRegisterForRemoteNotificationsWithDeviceToken:` message.
|
||||||
|
extern fn registered_for_remote_notifications<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the application receives a `application:didFailToRegisterForRemoteNotificationsWithError:` message.
|
||||||
|
extern fn failed_to_register_for_remote_notifications<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the application receives a `application:didReceiveRemoteNotification:` message.
|
||||||
|
extern fn did_receive_remote_notification<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires when the application receives a `application:userDidAcceptCloudKitShareWithMetadata:`
|
||||||
|
/// message.
|
||||||
|
extern fn accepted_cloudkit_share<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 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<T: AppController>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
|
||||||
|
let filename = str_from(file);
|
||||||
|
|
||||||
|
match app::<T>(this).open_file_without_ui(filename) {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when the application receives an `applicationShouldOpenUntitledFile:` message.
|
||||||
|
extern fn should_open_untitled_file<T: AppController>(this: &Object, _: Sel, _: id) -> BOOL {
|
||||||
|
match app::<T>(this).should_open_untitled_file() {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when the application receives an `applicationOpenUntitledFile:` message.
|
||||||
|
extern fn open_untitled_file<T: AppController>(this: &Object, _: Sel, _: id) -> BOOL {
|
||||||
|
match app::<T>(this).open_untitled_file() {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when the application receives an `application:openTempFile:` message.
|
||||||
|
extern fn open_temp_file<T: AppController>(this: &Object, _: Sel, _: id, filename: id) -> BOOL {
|
||||||
|
let filename = str_from(filename);
|
||||||
|
|
||||||
|
match app::<T>(this).open_temp_file(filename) {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fired when the application receives an `application:printFile:` message.
|
||||||
|
extern fn print_file<T: AppController>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
|
||||||
|
let filename = str_from(file);
|
||||||
|
|
||||||
|
match app::<T>(this).print_file(filename) {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
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<T: AppController>(this: &Object, _: Sel, _: id, key: id) -> BOOL {
|
||||||
|
let key = str_from(key);
|
||||||
|
|
||||||
|
match app::<T>(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<T: AppController>() -> *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::<usize>(APP_PTR);
|
||||||
|
|
||||||
|
// Launching Applications
|
||||||
|
decl.add_method(sel!(applicationWillFinishLaunching:), will_finish_launching::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching::<T> as extern fn(&Object, _, _));
|
||||||
|
|
||||||
|
// Managing Active Status
|
||||||
|
decl.add_method(sel!(applicationWillBecomeActive:), will_become_active::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationDidBecomeActive:), did_become_active::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationWillResignActive:), will_resign_active::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationDidResignActive:), did_resign_active::<T> as extern fn(&Object, _, _));
|
||||||
|
|
||||||
|
// Terminating Applications
|
||||||
|
decl.add_method(sel!(applicationShouldTerminate:), should_terminate::<T> as extern fn(&Object, _, _) -> NSUInteger);
|
||||||
|
decl.add_method(sel!(applicationWillTerminate:), will_terminate::<T> as extern fn(&Object, _, _));
|
||||||
|
|
||||||
|
// Hiding Applications
|
||||||
|
decl.add_method(sel!(applicationWillHide:), will_hide::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationDidHide:), did_hide::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationWillUnhide:), will_unhide::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationDidUnhide:), did_unhide::<T> as extern fn(&Object, _, _));
|
||||||
|
|
||||||
|
// Managing Windows
|
||||||
|
decl.add_method(sel!(applicationWillUpdate:), will_update::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationDidUpdate:), did_update::<T> as extern fn(&Object, _, _));
|
||||||
|
decl.add_method(sel!(applicationShouldHandleReopen:hasVisibleWindows:), should_handle_reopen::<T> as extern fn(&Object, _, _, BOOL) -> BOOL);
|
||||||
|
|
||||||
|
// Dock Menu
|
||||||
|
decl.add_method(sel!(applicationDockMenu:), dock_menu::<T> as extern fn(&Object, _, _) -> id);
|
||||||
|
|
||||||
|
// Displaying Errors
|
||||||
|
decl.add_method(sel!(application:willPresentError:), will_present_error::<T> as extern fn(&Object, _, _, id) -> id);
|
||||||
|
|
||||||
|
// Managing the Screen
|
||||||
|
decl.add_method(sel!(applicationDidChangeScreenParameters:), did_change_screen_parameters::<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);
|
||||||
|
decl.add_method(sel!(application:continueUserActivity:restorationHandler:), continue_user_activity::<T> as extern fn(&Object, _, _, id, id) -> BOOL);
|
||||||
|
decl.add_method(sel!(application:didFailToContinueUserActivityWithType:error:), failed_to_continue_user_activity::<T> as extern fn(&Object, _, _, id, id));
|
||||||
|
decl.add_method(sel!(application:didUpdateUserActivity:), did_update_user_activity::<T> as extern fn(&Object, _, _, id));
|
||||||
|
|
||||||
|
// Handling push notifications
|
||||||
|
decl.add_method(sel!(application:didRegisterForRemoteNotificationsWithDeviceToken:), registered_for_remote_notifications::<T> as extern fn(&Object, _, _, id));
|
||||||
|
decl.add_method(sel!(application:didFailToRegisterForRemoteNotificationsWithError:), failed_to_register_for_remote_notifications::<T> as extern fn(&Object, _, _, id));
|
||||||
|
decl.add_method(sel!(application:didReceiveRemoteNotification:), did_receive_remote_notification::<T> as extern fn(&Object, _, _, id));
|
||||||
|
|
||||||
|
// CloudKit
|
||||||
|
decl.add_method(sel!(application:userDidAcceptCloudKitShareWithMetadata:), accepted_cloudkit_share::<T> as extern fn(&Object, _, _, id));
|
||||||
|
|
||||||
|
// Opening Files
|
||||||
|
decl.add_method(sel!(application:openURLs:), open_urls::<T> as extern fn(&Object, _, _, id));
|
||||||
|
decl.add_method(sel!(application:openFileWithoutUI:), open_file_without_ui::<T> as extern fn(&Object, _, _, id) -> BOOL);
|
||||||
|
decl.add_method(sel!(applicationShouldOpenUntitledFile:), should_open_untitled_file::<T> as extern fn(&Object, _, _) -> BOOL);
|
||||||
|
decl.add_method(sel!(applicationOpenUntitledFile:), open_untitled_file::<T> as extern fn(&Object, _, _) -> BOOL);
|
||||||
|
decl.add_method(sel!(application:openTempFile:), open_temp_file::<T> as extern fn(&Object, _, _, id) -> BOOL);
|
||||||
|
|
||||||
|
// Printing
|
||||||
|
decl.add_method(sel!(application:printFile:), print_file::<T> as extern fn(&Object, _, _, id) -> BOOL);
|
||||||
|
decl.add_method(sel!(application:printFiles:withSettings:showPrintPanels:), print_files::<T> 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::<T> 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +1,21 @@
|
||||||
//! A wrapper for `NSApplicationDelegate` on macOS. Handles looping back events and providing a very janky
|
//! A wrapper for `NSApplicationDelegate` on macOS. Handles looping back events and providing a very janky
|
||||||
//! messaging architecture.
|
//! messaging architecture.
|
||||||
|
|
||||||
use std::sync::Once;
|
|
||||||
|
|
||||||
use cocoa::base::{id, nil};
|
use cocoa::base::{id, nil};
|
||||||
use cocoa::appkit::{NSRunningApplication};
|
use cocoa::appkit::{NSRunningApplication};
|
||||||
|
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
use objc::declare::ClassDecl;
|
use objc::runtime::Object;
|
||||||
use objc::runtime::{Class, Object, Sel};
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
mod class;
|
||||||
|
pub mod traits;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
use crate::constants::APP_PTR;
|
use crate::constants::APP_PTR;
|
||||||
use crate::menu::Menu;
|
use crate::menu::Menu;
|
||||||
|
use class::{register_app_class, register_app_controller_class};
|
||||||
mod events;
|
pub use traits::{AppController, Dispatcher};
|
||||||
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) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime,
|
/// 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,
|
/// which is where our application instance lives. It also injects an `NSObject` subclass,
|
||||||
|
@ -61,7 +50,7 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate + Dispatcher<Message = M> {
|
impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppController + Dispatcher<Message = M> {
|
||||||
/// Dispatches a message by grabbing the `sharedApplication`, getting ahold of the delegate,
|
/// 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.
|
/// and passing back through there. All messages are currently dispatched on the main thread.
|
||||||
pub fn dispatch(message: M) {
|
pub fn dispatch(message: M) {
|
||||||
|
@ -97,7 +86,7 @@ impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate + Dispatcher
|
||||||
let app_delegate = Box::new(delegate);
|
let app_delegate = Box::new(delegate);
|
||||||
|
|
||||||
let objc_delegate = unsafe {
|
let objc_delegate = unsafe {
|
||||||
let delegate_class = register_delegate_class::<T>();
|
let delegate_class = register_app_controller_class::<T>();
|
||||||
let delegate: id = msg_send![delegate_class, new];
|
let delegate: id = msg_send![delegate_class, new];
|
||||||
let delegate_ptr: *const T = &*app_delegate;
|
let delegate_ptr: *const T = &*app_delegate;
|
||||||
(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
|
(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
|
||||||
|
@ -114,7 +103,7 @@ impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate + Dispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
|
/// 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`. :)
|
/// `did_finish_launching`. :)
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -125,45 +114,3 @@ impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate + Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
|
|
||||||
extern fn did_finish_launching<D: AppDelegate>(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<D: AppDelegate>(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<D: AppDelegate>() -> *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::<usize>(APP_PTR);
|
|
||||||
|
|
||||||
// Add callback methods
|
|
||||||
decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching::<D> as extern fn(&Object, _, _));
|
|
||||||
decl.add_method(sel!(applicationDidBecomeActive:), did_become_active::<D> as extern fn(&Object, _, _));
|
|
||||||
|
|
||||||
DELEGATE_CLASS = decl.register();
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
DELEGATE_CLASS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
195
appkit/src/app/traits.rs
Normal file
195
appkit/src/app/traits.rs
Normal file
|
@ -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<Menu> { 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<Url>) { }
|
||||||
|
|
||||||
|
/// 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<String>, _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 }
|
||||||
|
}
|
60
appkit/src/app/types.rs
Normal file
60
appkit/src/app/types.rs
Normal file
|
@ -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<TerminateResponse> 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<PrintResponse> for NSUInteger {
|
||||||
|
fn from(response: PrintResponse) -> NSUInteger {
|
||||||
|
match response {
|
||||||
|
PrintResponse::Cancelled => 0,
|
||||||
|
PrintResponse::Success => 1,
|
||||||
|
PrintResponse::Failure => 3,
|
||||||
|
PrintResponse::ReplyLater => 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,9 @@
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use cocoa::base::id;
|
use cocoa::base::{id, nil};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use cocoa::foundation::{NSInteger, NSString};
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::utils::str_from;
|
use crate::utils::str_from;
|
||||||
|
|
||||||
|
@ -40,6 +41,17 @@ impl AppKitError {
|
||||||
description: str_from(description).to_string()
|
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 {
|
impl fmt::Display for AppKitError {
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub mod menu;
|
||||||
pub mod networking;
|
pub mod networking;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
pub mod pasteboard;
|
pub mod pasteboard;
|
||||||
|
pub mod printing;
|
||||||
pub mod toolbar;
|
pub mod toolbar;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
@ -46,7 +47,7 @@ pub mod window;
|
||||||
pub use url;
|
pub use url;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::app::{App, AppDelegate, Dispatcher};
|
pub use crate::app::{App, AppController, Dispatcher};
|
||||||
|
|
||||||
pub use crate::layout::LayoutConstraint;
|
pub use crate::layout::LayoutConstraint;
|
||||||
|
|
||||||
|
|
5
appkit/src/printing/mod.rs
Normal file
5
appkit/src/printing/mod.rs
Normal file
|
@ -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::*;
|
4
appkit/src/printing/types.rs
Normal file
4
appkit/src/printing/types.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
//! Represents settings for printing items.
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct PrintSettings;
|
Loading…
Reference in a new issue