Sandboxed Cocoa Application Groups - cocoa

The entitlement documentation describes a method in which multiple apps produced by a single development team can share access to a special group container
I've added the entitlement key to a main application and a helper application that is included in the main application bundle. The group directory is never created, and the method in which to get the path – [NSURL containerURLForSecurityApplicationGroupIdentifier:] – doesn't actually exist in any header.
What am I missing to create a shared group container?
UPDATE : added entitlements and group container creation
Here is my entitlements file for the main app and helper application. (replacing TEAM_ID with my actual ID)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>TEAM_ID.com.pinepointsoftware</string>
</array>
</dict>
</plist>
I've attempted to create the Group Container directory myself based on some information from the dev forums:
NSFileManager *fm = [NSFileManager defaultManager];
NSString *path = [#"~/Library/Group Containers/TEAM_ID.com.pinepointsoftware" stringByExpandingTildeInPath];
if ([fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:NULL]) {
[fm createFileAtPath:[path stringByAppendingPathComponent:#"test.txt"] contents:nil attributes:nil];
}
Executing that inside the main and and helper app create two different directories inside their each sandbox.
I found a sample project from Apple AppSandboxLoginItemXPCDemo, that uses the application groups for XPC. I can get it working by changing the entitlements and bundle identifiers to match my team, but cannot get the Group Containers to be shared.

The "~" will be automatically converted to your container directory in a sandboxed environment.
So one way to do it is to append the "~" with a number of step back ("../") until you reach ~/Library and then you create your group container starting from it.

Related

Not able to connect to XPCService in a Launch agent

I have created an XPC Service .
the server side code briefly is
NSXPCListener *listener = [[NSXPCListener alloc] initWithMachServiceName:#"test.xpcserver"];
listener.delegate = delegate;
[listener resume];
this is installed as a Launch Agent using info.plist as
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>TestServer</string>
<key>MachServices</key>
<dict>
<key>test.xpcserver</key>
<true/>
</dict>
<key>ProgramArguments</key>
<array>
<string>/Applications/test.app/Contents/XPCServices/xpcserver.xpc/Contents/MacOS/xpcserver</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
The server is running fine and i can see it running with launchctl list
The client side code is in another app and the code for connect is:
connection = [[NSXPCConnection alloc] initWithMachServiceName:#“test.xpcserver” options:0];
connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(xpcserverprotocol)];
[connection resume];
service = [connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) { }];
[service ServerFunc:#“howdy” withReply:^(NSString *result) {
NSLog(#"%#",result);
}];
but not able to connect to server .
any pointers as to what going wrong ?
It's hard to tell from what you've posted. Things to check:
ensure your listener delegate is implementing right function signature for shouldAcceptNewConnection(). If it's not right, you will not get any error. It should be
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)conn { your code here }
Use System Logging (NSLog) or add logging of stdout and stderr for your service. See the launch.sh in this hello_world_xpc example, which generates the plist based on local paths. That example is using the C API, so not exactly what you are looking for.
See old Objective-C hello.tar.gz example here : afewguyscoding.com/2012/07/ipc-easy-introducing-xpc-nsxpcconnection/
(Posting this for future readers, I imagine OP solved their problem by now :) )
Some other things to check:
Make sure your launch agent/deamon is using the -[NSXPCListener initWithMachServiceName], and not +[NSXPCListener serviceListener].
The latter is only useful for regular XPC service (which would be stored in the App's bundle, not in /Library/.... If you mix it up, you'll get a crash report in Console.app that will say:
Configuration error: Couldn't retrieve XPCService dictionary from service bundle.
In your agent/daemon, don't forget to start the main run loop after [listener resume]; with [[NSRunLoop currentRunLoop] run];
In your agent/daemon, make sure there's a strong reference to keep your listener's delegate alive. The -[NSXPCListener delegate] property is declared weak, so it's possible that your listener deallocates immediately after you set it. I would put an NSLog statement in the deinit of your delegate's class, just to be sure.

Xamarin Macintosh Customer URL protocol handle passed parameter

I've written a Macintosh app that handles a custom protocol:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>My Cool Handler</string>
<key>CFBundleURLSchemes</key>
<array>
<string>coolhandler</string>
</array>
</dict>
</array>
All well and good. It launches. However, I'm clicking on a link like this:
coolhandler://Iwant/toparse/this
In Windows, the registry entry is simple and this work just fine. When my Windows app launches, the whole url is passed as an argument and I can parse it.
protected override void OnStartup(StartupEventArgs e)
{
_url = !e.Args.Any()?"":e.Args[0];
//parse the url
}
Where, in my pList or in the app do I handle this? i.e, how do I pass the url argument to the app?
You'll need to do something like this in C# likely:
Accessing command line arguments in Objective-C
Which would look something like:
string[] args = NSProcessInfo.ProcessInfo.Arguments;

How to register service from app in macOS application?

I am trying to implement a contextual menu item that would be presented in services when a text is selected. So e.g., if I select a word in a TextEdit, I want a menu item "Do my stuff" to be presented in contextual menu, which would feed the selected word to my application code for further processing.
By googling a little research, I came to conclusion that I need to implement and register a service. I tried to follow
Providing Services
documentation, but that seems at least a bit outdated (not to mention in some places vague).
From what I understood, I was able to do the following:
I implemented service object:
import Foundation
import AppKit
class ContextualMenuServiceProvider {
func importString(_ pasteBoard: Pasteboard, userData: String, error: String) {
print(">>>> in import string from service consumer")
}
}
I created an entry in Info.plist for service:
<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Import to $(PRODUCT_NAME)</string>
</dict>
<key>NSMessage</key>
<string>importString</string>
<key>NSPortName</key>
<string>MyApp</string>
<key>NSSendTypes</key>
<array>
<string>public.plain-text</string>
</array>
</dict>
</array>
And finally, I tried to register the service in AppDelegate. Now the documentation says to use:
NSApp.setServicesProvider(encryptor)
// where encryptor is an object of my ContextualMenuServiceProvider
However, it seems that NSApp does not have setServicesProvider method. I tried to use:
NSApp.servicesProvider = ContextualMenuServiceProvider()
property, however, this does not seem to work either.
I test it by running the application from the Xcode, and then, while the application is running, I try to select some text in TextEdit (it should not be restricted to TextEdit, I just use it as an example), and after right click I am looking for my menu item. It does not work.
I was able to find some kind of similar SO questions (e.g., Creating an os x service, or Howto add menu item to Mac OS Finder in Delphi XE2), but I was not able to detect what I am doing wrong from them (not to mention that most of them are old (using setServiceProvider), or using languages like C# or Delphi).
Any idea what is wrong with my code/approach?
Ok, looks like I have made several small mistakes that lead me to believe it was not working. However, in the end I was able to make it work. So if you are facing the same task, here is a solution in Swift 3:
(1) You have to implement a service method:
import Cocoa
class ServiceProvider: NSObject {
let errorMessage = NSString(string: "Could not find the text for parsing.")
#objc func service(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer<NSString>) {
guard let str = pasteboard.string(forType: NSStringPboardType) else {
error.pointee = errorMessage
return
}
let alert = NSAlert()
alert.messageText = "Hello \(str)"
alert.informativeText = "Welcome in the service"
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
Here important thing that I did not know before was that the object providing the service has to subclass NSObject (it can also be ViewController, or any other NSObject subclass). Services API uses selector to call it, and selector technology works with NSObject only.
Also, service method has to have this declaration: it takes 3 arguments, first is NSPasteboard (you can use Pasteboard, but that does not provide string method) that is not labeled, second is optional String labeled userData, third is AutoreleasingUnsafeMutablePointer<NSString> labeled error. Follow this to get best type safety while still making it work. Otherwise the services API won't be able to find it and call it. You can use UnsafeRawPointers for all its arguments, but you will not gain anything by that.
(2) In the app delegate (or documentation says that you can do it anywhere, but I am doing it here) you register the service provider:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApplication.shared().servicesProvider = ServiceProvider()
NSUpdateDynamicServices()
}
}
It seems to be working also without NSUpdateDynamicServices call, but I guess better be safe than sorry - it should refresh the services in the system so that you do not have to logoff and login the user to get the current service version.
(3) Finally, you have to configure the Info.plist to advertise your service method:
<key>NSServices</key>
<array>
<dict>
<key>NSMessage</key>
<string>service</string>
<key>NSPortName</key>
<string>serviceTest</string>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Test hello world</string>
</dict>
<key>NSRestricted</key>
<false/>
<key>NSRequiredContext</key>
<dict/>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
</dict>
</array>
This is an XML source of the Info.plist - although Apple recommends to use their editor, in Xcode 8.2 I was not able to add an NSRequiredContext key through the editor - I had to open the file as Source code and add it manually. I would recommend editing directly the XML source.
You can find the documentation about the meaning of each key in Services Properties, but I would like to mention some crucial points. First, you NEED the NSRequiredContext key - they mention it in the documentation, but the editor does not support it - edit XML directly and add it even empty (as I do). Second, if you are using NSSendTypes or NSReturnTypes and you want to work with strings, use NSStringPboardType even though its documentation says it's deprecated and should be substituted by NSPasteboardTypeString - the latter won't work. Finally, NSMessage key with service value is the name of the service method. So my service provider object declares following method:
#objc func service(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer<NSString>)
I am using the service value in NSMessage. If you want to change it (e.g., you want several service methods) just don't forget that these two has to match (value in Info.plist configuration and the name of the service method).
(4) In this state it should be working. There is one thing I would like to mention that might help you with debugging. Test it using the method mentioned in documentation at the end by running:
/Applications/TextEdit.app/Contents/MacOS/TextEdit -NSDebugServices com.mycompany.myapp
Running this from Terminal should report whether any service using provided bundle is registered, and also whether it will be presented - e.g., in my case the problem was that I had not provided the mandatory NSRequiredContext. After testing it using this approach I was able to recognise that the service was installed, and the problem was that the services API assumed it should be filtered out. After some experiments and googling I solved it by adding the empty NSRequiredContext.
After each change to the service I recommend to quit TextEdit and run it again, it seems that it keeps a reference to the old service provider object (or something like that, changes were not registered by TextEdit if I did not restart it).
The minimum requirements for text services in info.plist. All items can be added through Xcode (tested on version 14.2):
<key>NSServices</key>
<array>
<dict>
<key>NSMessage</key>
<string>message</string>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Title</string>
</dict>
<key>NSRequiredContext</key>
<dict/>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
</dict>
</array>
In AppDelegate:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.servicesProvider = self
}
#objc func message(_ pasteboard: NSPasteboard, userData: String?, error: NSErrorPointer) {
guard let content = pasteboard.string(forType: .string) else { return }
// Use `content`…
}
}
The system won’t update services every time after app’s launching. Remove the app entirely, then build and run again so the services can be refreshed. Run pbs to check if the service is correctly configured under your bundle identifier:
/System/Library/CoreServices/pbs -dump_pboard

How to register for “Apps using location” on the Mac

I’m playing with Wi-Fi Positioning system (WPS) on the Mac.
Attempting to specify the most exact positioning possible. I’ve noticed that apps such as Maps register in the Location notification of the OS
Yet my code does not seem to generate this effect,
- (void)startStandardUpdates
{
// Create the location manager if this object does not
// already have one.
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
// Set a movement threshold for new events.
locationManager.distanceFilter = 1; // meters
[locationManager startUpdatingLocation];
}
Am I missing something?
Is your app sandboxed?
If it is, you need to enable the "App Data" -> "Location" entitlement in the "Capabilities" tab of your target.
Otherwise the CLLocationManager silently fails to start.
Another interesting point is, that your app only shows up in this list when the CLLocationManager is actually running. Calling stopUpdatingLocation deregisters your app from the location agent.
A minimal entitlement file for a OS X app using Core Location looks like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
</dict>
</plist>

When an OS X app is launched by a registered URL scheme, how do you access the full URL?

I'm working on an Cocoa app which is launched/activated using URLs with a custom scheme which is registered in the Info.plist file like so:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Open myscheme:// URLs</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myscheme</string>
</array>
</dict>
</array>
My question is, once the app is launched or activated, how do I tell what the URL was that launched the app? On iOS, this is easy with the -application:openURL:sourceApplication:annotation: method on the UIApplicationDelegate since it is passed an NSURL instance.
I want to be able to pass in data into my app with URLs like myscheme://do/something/awesome
In your app delegate's -applicationWillFinishLaunching:, do:
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:#selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
And handleAppleEvent:withReplyEvent: should look something like:
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
// do something with the URL string
}

Resources