Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Definition of NSWorkspace, NSNotificationCenter, and NSRunningApplication #101

Open
wants to merge 9 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ objc_id = "0.1.1"
os_info = "3.0.1"
url = "2.1.1"
uuid = { version = "1.1", features = ["v4"], optional = true }
strum = "0.25"
strum_macros = "0.25"

[dev-dependencies]
eval = "0.4"
Expand Down
2 changes: 1 addition & 1 deletion src/appkit/app/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::appkit::printing::PrintSettings;
#[cfg(feature = "cloudkit")]
use crate::cloudkit::share::CKShareMetaData;
use crate::error::Error;
use crate::foundation::{id, load_or_register_class, nil, to_bool, NSArray, NSString, NSUInteger, BOOL, NO, YES};
use crate::foundation::{id, load_or_register_class, nil, to_bool, NSArray, NSString, NSUInteger, Retainable, BOOL, NO, YES};
use crate::user_activity::UserActivity;

/// A handy method for grabbing our `AppDelegate` from the pointer. This is different from our
Expand Down
2 changes: 1 addition & 1 deletion src/appkit/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id;

use crate::events::EventType;
use crate::foundation::{id, nil, NSInteger, NSPoint, NSString};
use crate::foundation::{id, nil, NSInteger, NSPoint, NSString, Retainable};

/// An EventMask describes the type of event.
#[bitmask(u64)]
Expand Down
1 change: 1 addition & 0 deletions src/appkit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ pub mod toolbar;
pub mod window;

pub mod haptics;
pub mod workspace;
2 changes: 1 addition & 1 deletion src/appkit/toolbar/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};

use crate::appkit::toolbar::{ToolbarDelegate, TOOLBAR_PTR};
use crate::foundation::{id, load_or_register_class, NSArray, NSString, BOOL};
use crate::foundation::{id, load_or_register_class, NSArray, NSString, Retainable, BOOL};
use crate::utils::load;

/// Retrieves and passes the allowed item identifiers for this toolbar.
Expand Down
313 changes: 313 additions & 0 deletions src/appkit/workspace/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
use std::collections::HashMap;

use block::ConcreteBlock;
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
use objc_id::Id;

use crate::{
color::Color,
error::Error,
foundation::{id, nil, NSArray, NSInteger, NSMutableDictionary, NSString, Retainable, NSURL},
notification_center::NotificationCenter,
};

use self::running_application::RunningApplication;

pub mod running_application;

#[derive(Debug)]
pub struct Workspace(Id<Object>);

#[derive(Debug)]
pub struct GetFileSystemInfoForPathResponse {
pub removable: bool,
pub writable: bool,
pub unmountable: bool,
pub description: String,
pub file_system_type: String,
}

impl Workspace {
/// The shared workspace object.
pub fn shared() -> Self {
let workspace: id = unsafe { msg_send![class!(NSWorkspace), sharedWorkspace] };

Workspace::retain(workspace)
}

// Accessing the Workspace Notification Center

/// The notification center for workspace notifications.
pub fn notification_center(&self) -> NotificationCenter {
let notification: id = unsafe { msg_send![self.0, notificationCenter] };

NotificationCenter::retain(notification)
}

// Opening URLs

/// Opens a URL asynchronously using the provided options.
///
/// Maps to `openURL:configuration:completionHandler:`
pub fn open_url_with_completion<F>(&self, _url: &str, _configuration: &str, _completion_handler: F)
where
F: FnOnce() -> (),
{
unimplemented!("Missing NSWorkspaceOpenConfiguration implementation. Only >=10.15");
}

/// Opens one or more URLs asynchronously in the specified app using the provided options.
pub fn open_url_with_application_with_completion<F>(
&self,
_url: &str,
_application_url: &str,
_configuration: &str,
_completion_handler: F,
) where
F: FnOnce() -> (),
{
unimplemented!("Missing NSWorkspaceOpenConfiguration implementation. Only >=10.15");
}

/// Opens the location at the specified URL.
pub fn open_url(&self, url: &str) -> bool {
let url = NSString::new(url);

unsafe {
let url: id = msg_send![class!(NSURL), URLWithString:url];
msg_send![self.0, openURL: url]
}
}

// Launching and Hiding Apps

/// Launches the app at the specified URL and asynchronously reports back on the app's status.
pub fn open_application_at_url<F>(&self, _application_url: &str, _configuration: &str, _completion_handler: F)
where
F: FnOnce() -> (),
{
unimplemented!("Missing NSWorkspaceOpenConfiguration implementation. Only >=10.15");
}

/// Hides all applications other than the sender.
///
/// Must be called on the main thread
pub fn hide_other_applications(&self) {
unsafe { msg_send![self.0, hideOtherApplications] }
}

/// Duplicates the specified URLS asynchronously in the same manner as the Finder.
pub fn duplicate_urls<F>(&self, _urls: Vec<&str>, _completion_handler: Option<F>)
where
F: Fn(HashMap<String, String>, Error) -> () + Send + Sync + 'static,
{
unimplemented!("Missing ability to create NSArrays of NSURL");
// let urls = urls
// .iter()
// .map(|u| {
// let mut url = NSURL::with_str(u);
// &mut *url as id
// })
// .collect::<Vec<id>>();
// let urls = NSArray::new(&urls);

// if let Some(completion_handler) = completion_handler {
// let block = ConcreteBlock::new(move |new_urls, error| {
// let new_urls = NSMutableDictionary::retain(new_urls);
// let new_urls = new_urls.into_hashmap(|u| NSURL::retain(u).absolute_string());

// let error = Error::new(error);

// completion_handler(new_urls, error);
// });

// let block = block.copy();

// unsafe { msg_send![self.0, duplicateURLs: urls completionHandler: block] }
// } else {
// unsafe { msg_send![self.0, duplicateURLs: urls completionHandler: nil] }
// }
}

/// Moves the specified URLs to the trash in the same manner as the Finder.
pub fn recycle_urls<F>(&self, _urls: Vec<&str>, _completion_handler: Option<F>)
where
F: Fn(HashMap<String, String>, Error) -> () + Send + Sync + 'static,
{
unimplemented!("Missing ability to create NSArrays of NSURL");
}

/// Activates the Finder, and opens one or more windows selecting the specified files.
pub fn active_file_viewer_selecting_urls(&self, _urls: Vec<&str>) {
unimplemented!("Missing ability to create NSArrays of NSURL");
}

/// Selects the file at the specified path. Corresponds to `selectFile:inFileViewerRootedAtPath:`
pub fn select_file(&self, file_path: &str, file_viewer_root_path: &str) -> bool {
let file_path = NSString::new(file_path);
let root_path = NSString::new(file_viewer_root_path);

unsafe { msg_send![self.0, selectFile: file_path inFileViewerRootedAtPath: root_path] }
}

// Manipulating Uniform Type Identifier Information

/// Returns the URL for the app with the specified identifier.
pub fn url_for_application(&self, bundle_identifier: &str) -> Option<NSURL> {
let bundle_identifier = NSString::new(bundle_identifier);

let url: id = unsafe { msg_send![self.0, URLForApplicationWithBundleIdentifier: bundle_identifier] };
NSURL::retain_nullable(url)
}

// Requesting Information

/// Returns the URL to the default app that would be opened.
pub fn url_for_application_to_open(&self, url: &str) -> Option<NSURL> {
let url = NSString::new(url);

let url: id = unsafe { msg_send![self.0, URLForApplicationToOpenURL: url] };
NSURL::retain_nullable(url)
}

/// Returns information about the file system at the specified path.
pub fn get_fs_info(&self, path: &str) -> Option<GetFileSystemInfoForPathResponse> {
let path = NSString::new(path);

let mut removable = Box::new(false);
let mut writable = Box::new(false);
let mut unmountable = Box::new(false);
let description = NSString::new("");
let file_system_type = NSString::new("");

let removable_ptr = removable.as_mut() as *mut bool;
let writable_ptr = writable.as_mut() as *mut bool;
let unmountable_ptr = unmountable.as_mut() as *mut bool;

let returned_data: bool = unsafe {
msg_send![self.0, getFileSystemInfoForPath:
path isRemovable: removable_ptr
isWritable: writable_ptr
isUnmountable: unmountable_ptr
description: &description
type: &file_system_type
]
};

if returned_data {
Some(GetFileSystemInfoForPathResponse {
removable: *removable,
writable: *writable,
unmountable: *unmountable,
description: description.to_string(),
file_system_type: file_system_type.to_string(),
})
} else {
None
}
}

/// Determines whether the specified path is a file package.
pub fn is_path_file_package(&self, path: &str) -> bool {
let path = NSString::new(path);

unsafe { msg_send![self.0, isFilePackageAtPath: path] }
}

/// Returns the frontmost app, which is the app that receives key events.
pub fn frontmost_application(&self) -> Option<RunningApplication> {
let id: id = unsafe { msg_send![self.0, frontmostApplication] };

if !id.is_null() {
Some(RunningApplication::retain(id))
} else {
None
}
}

/// Returns an array of running apps.
pub fn running_applications(&self) -> Vec<RunningApplication> {
let apps: id = unsafe { msg_send![self.0, runningApplications] };

NSArray::retain(apps).iter().map(|a| RunningApplication::retain(a)).collect()
}

/// Returns the app that owns the currently displayed menu bar.
pub fn menu_bar_owning_application(&self) -> Option<RunningApplication> {
let id: id = unsafe { msg_send![self.0, menuBarOwningApplication] };

if !id.is_null() {
Some(RunningApplication::retain(id))
} else {
None
}
}

// Managing Icons

// TODO: Need icon methods

// Unmounting a Device

// TODO: Need unmount methods

// Managing the Desktop Image

// TODO: Need desktop image methods

// Performing Finder Spotlight Searches

/// Displays a Spotlight search results window in Finder for the specified query string.
pub fn show_finder_results_for_query(&self, query: &str) -> bool {
let query = NSString::new(query);
unsafe { msg_send![self.0, showSearchResultsForQueryString: query] }
}

// Finder File Labels

/// The array of file labels, returned as strings.
pub fn file_labels(&self) -> Vec<String> {
let id: id = unsafe { msg_send![self.0, fileLabels] };
NSArray::retain(id).iter().map(|s| NSString::retain(s).to_string()).collect()
}

/// The array of colors for the file labels.
pub fn file_label_colors(&self) -> Vec<Color> {
unimplemented!("No NSColor constructor from a pointer")
}

// Tracking Changes to the File System

/// Informs the workspace object that the file system changed at the specified path.
pub fn note_fs_changed(&self, path: &str) {
let path = NSString::new(path);
unsafe { msg_send![self.0, noteFileSystemChanged: path] }
}

// Requesting Additional Time Before Logout

/// Requests the system wait for the specified amount of time before turning off the power or logging out the user.
pub fn extend_power_off(&self, milliseconds: i64) {
let milliseconds = milliseconds as NSInteger;
unsafe { msg_send![self.0, extendPowerOffBy: milliseconds] }
}

// Suppporting Accessibility

// TODO: Need accessibility getter/setter methods

// Performing Priviledged Operations

// TODO: Needs priviledged op methods - This returns an opaque object that is passed to NSFileManager,
// so probably not terribly important
}

impl Retainable for Workspace {
fn retain(handle: id) -> Self {
Workspace(unsafe { Id::from_ptr(handle) })
}

fn from_retained(handle: id) -> Self {
Workspace(unsafe { Id::from_retained_ptr(handle) })
}
}
Loading
Loading