I had asked a previous question about an issue I was having with AVPlayerView. After toying with it a bit, I actually like my mac app looks and runs a lot better when, instead of opening a new window with an AVPlayerView, it launches QuickTime, and tells it to open my http url of a video. I have come up with a few ways to do this, all of which work without sandboxing and none of which work with. I am currently using an NSTask to essentially
open -a "Quicktime Player" "http://example.com/video.m4v"
Again, this works, but only when my app is not sandboxed. Is there any way to do this in a sandboxed app?
Thanks in advance for any input or suggestions.
Sandbox doesn't allow to work with NSTask by default. Use the appropriate entitlement.
To open quicktime and start playing a movie you're probably better off using NSWorkspace like this:
[[NSWorkspace sharedWorkspace] openURLs:#[url]
withAppBundleIdentifier:#"com.apple.QuickTimePlayerX"
options:NSWorkspaceLaunchAsync
additionalEventParamDescriptor:NULL
launchIdentifiers:nil];
Alternatively go straight down to Launch Services where you even have more control of what and how things are launched:
NSURL *appToOpenWith = // get the URL of Quicktime using NSWorkspace URLForApplicationWithBundleIdentifier;
LSLaunchURLSpec inLaunchSpec;
inLaunchSpec.appURL = (__bridge CFURLRef) appToOpenWith;
inLaunchSpec.itemURLs = (__bridge CFArrayRef) ([NSArray arrayWithObject:theNSURLPointingToYourM4V ]);
inLaunchSpec.passThruParams = NULL;
inLaunchSpec.launchFlags = kLSLaunchDefaults; // could be done async... are we here in the main thread?
inLaunchSpec.asyncRefCon = NULL;
CFURLRef outLaunchedURL;
OSStatus diditOpen = LSOpenFromURLSpec (&inLaunchSpec, &outLaunchedURL);
if (noErr != diditOpen) {
NSLog(#"couldn't open selected items with error: %i", diditOpen);
} else {
NSLog(#"opened with: %#", [(__bridge NSURL*)outLaunchedURL description]);
}
Related
One of my app's feature is detect keyDown event and do someting for some specific keys.
So, I use CGEventTapCreate to solve my problem and my code is like this:
if let tap = CGEventTapCreate(.CGHIDEventTap, .HeadInsertEventTap, .Default, CGEventMask(1 << CGEventType.KeyDown.rawValue), callBack, nil) {
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode)
CGEventTapEnable(tap, true)
CFRunLoopRun()
}
Also, I deal with the process trust like:
let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString: false]
let processTrusted = AXIsProcessTrustedWithOptions(options)
if processTrusted {
// do CGEventTapCreate()
} else {
// check again 1s later
performSelector("checkMethod", withObject: nil, afterDelay: 1)
}
It will be called per second until processTrusted is true.
Then strange thing happened:
When I run the app in Xcode and add trust Xcode in Privacy_Accessibility it all work fine. But When I run it with archive the app and Export as a Mac Application to my desktop, CGEventTapCreate() just not work.
After that I found this post Enable access for assistive devices programmatically on 10.9, it notice me that I need to relaunch the app after AXIsTrustedProcess().
You know, it's not a good idea to tell users to relaunch the app themselves. So I try to relaunch it programmatically and add these code into if processTrusted {}:
NSWorkspace.sharedWorkspace().launchApplication(NSProcessInfo.processInfo().processName)
NSApplication.sharedApplication().terminate(nil)
In other words, when I tick the app in Privacy_Accessibility, it will relaunch automatically.
Here comes another strange thing:
The app truly terminate and launch automatically. But when it finish relaunch, the app's Privacy_Accessibility is not tick.
It's really confuse me a lot, I hope someone will tell me the right way to deal with process trust and execute CGEventTapCreate() correctly.
Thanks!
Polling AXIsProcessTrusted and automatically relaunching is a nice touch. Users are often confused by this process, so anything that helps make it easier for them is good.
I'm pretty sure that an application that you launch inherits your current permissions, but don't have references handy to back that up. I have found that when debugging an application with CGEventTaps, I also have to give Xcode the Accessibility permissions that my app requests/requires.
But you can work around this by getting the system to launch your app for you. Try:
[NSTask launchedTaskWithLaunchPath:#"/bin/sh" arguments:[NSArray arrayWithObjects:#"-c", [NSString stringWithFormat:#"sleep 3 ; /usr/bin/open '%#'", [[NSBundle mainBundle] bundlePath]], nil]];
For example, a user puts my application on his/her desktop. Then he/she copied (instead of moving) it to the /Application folder.
If the one at ~/Desktop has been launched, how can I prevent duplicated launching of the one at ~/Application? Any simple way?
Or if the user launches the one in /Application, I can detect the pre-launched app, and then switch to that immediately?
How about getting a list of all running applications and quitting immediately if your app detects there's another instance already running?
You can get a list of all active apps via NSWorkspace and runningApplications:
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
NSArray *allRunningApps = [ws runningApplications];
It'll return an array of NSRunningApplication instances so all you'd have to do is check the list for e.g. your app's ID via the bundleIdentifier method.
cacau's answer had given me inspiration to achieve my goal. Here are my codes (ARC):
- (BOOL)checkAppDuplicateAndBringToFrontWithBundle:(NSBundle *)bundle
{
NSRunningApplication *app;
NSArray *appArray;
NSUInteger tmp;
pid_t selfPid;
BOOL ret = NO;
selfPid = [[NSRunningApplication currentApplication] processIdentifier];
appArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:[bundle bundleIdentifier]];
for (tmp = 0; tmp < [appArray count]; tmp++)
{
app = [appArray objectAtIndex:tmp];
if ([app processIdentifier] == selfPid)
{
/* do nothing */
}
else
{
[[NSWorkspace sharedWorkspace] launchApplication:[[app bundleURL] path]];
ret = YES;
}
}
return ret;
}
It checks app duplicate by bundle identifier. As bundle duplicated, it brings all other applications to front and returns YES. As receiving YES, application can terminate itself.
However, as cacau has given me significant help, I gave him the reputation point of this question. Thank you!
Would it not be simplest to actually just create a file inside NSTempDirectory, check for it and if it is there, you quit, or perhaps offer a way to delete the file ?
This would work for both running in and out of sandbox.
I'm working on an app where using global key-down events will be a requirement for its operation. Additionally, I plan on distributing this strictly via the App Store. (It's a Mac app, not iOS.) I've gotten an example of listening for the global events working via addGlobalMonitorForEventsMatchingMask, but with caveats.
Note: I am making the choice to use the modern API's and not rely on the earlier Carbon hotkey methods. In the event that they are deprecated eventually, I don't want to have to figure this problem out later.
The principle issue is that the app has to be trusted in order for global events to be detected. Otherwise, accessibility has to be enabled for all apps. When I enable accessibility, events are detected successfully. This requirement is documented here, https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html.
I would prefer that for my users, they will not have to enable accessibility. From other research I've done, you can get an application to be trusted by calling AXMakeProcessTrusted, then restarting the application.
In the code that I'm using, I do not get an authentication prompt. The app will restart, but is still not trusted (likely because I don't get an authentication prompt). Here's my code for this part:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
if (!AXAPIEnabled() && !AXIsProcessTrusted()) {
NSString *appPath = [[NSBundle mainBundle] bundlePath];
AXError error = AXMakeProcessTrusted( (CFStringRef)CFBridgingRetain(appPath) );
[self restartApp];
}
}
- (void)restartApp{
NSTask *task = [[NSTask alloc] init];
NSMutableArray *args = [NSMutableArray array];
[args addObject:#"-c"];
[args addObject:[NSString stringWithFormat:#"sleep %d; open \"%#\"", 3, [[NSBundle mainBundle] bundlePath]]];
[task setLaunchPath:#"/bin/sh"];
[task setArguments:args];
[task launch];
[NSApp terminate:nil];
}
Further, I've looked at the documentation for Authorization Service Tasks here https://developer.apple.com/library/archive/documentation/Security/Conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG.
The first thing that worries me that pops out is this info box, "Important The authorization services API is not supported within an app sandbox because it allows privilege escalation."
If this API is required to get the authentication prompt before restarting the app, it seems that I may not be able to get global events without the accessibility feature enabled.
In summary, my specific questions are:
Is there an error in my sample code about how to get the
authentication prompt to appear?
In order to get the authentication prompt to appear, am I required
to use the Authorization Services API?
Is it possible, or not possible, to have a sandboxed app that has
access to global events?
First of all, there is no way you can automatically allow an app to use accessibility API which would work in a sandbox environment and thus in app store. The recommended way is to simply guide users so they can easily enable it themselves. The new API call AXIsProcessTrustedWithOptions is exactly for that:
NSDictionary *options = #{(id) kAXTrustedCheckOptionPrompt : #YES};
AXIsProcessTrustedWithOptions((CFDictionaryRef) options);
Now, to your first and second question (just for the sake of completeness - again it won't work in sandbox):
The idea behind AXMakeProcessTrusted was that you actually create a new auxiliary application that you run as root from the main application. This utility then calls AXMakeProcessTrusted passing in the executable of the main application. Finally you have to restart the main app. The API call has been deprecated in OSX 10.9.
To spawn a new process as a root you have to use launchd using SMJobSubmit. This will prompt a user with an authentication prompt saying that an application is trying to install a helper tool and whether it should be allowed. Concretely:
+ (BOOL)makeTrustedWithError:(NSError **)error {
NSString *label = FMTStr(#"%#.%#", kShiftItAppBundleId, #"mktrusted");
NSString *command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:#"mktrusted"];
AuthorizationItem authItem = {kSMRightModifySystemDaemons, 0, NULL, 0};
AuthorizationRights authRights = {1, &authItem};
AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
AuthorizationRef auth;
if (AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &auth) == errAuthorizationSuccess) {
// this is actually important - if from any reason the job was not removed, it won't relaunch
// to check for the running jobs use: sudo launchctl list
// the sudo is important since this job runs under root
SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
// this is actually the launchd plist for a new process
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist
NSDictionary *plist = #{
#"Label" : label,
#"RunAtLoad" : #YES,
#"ProgramArguments" : #[command],
#"Debug" : #YES
};
BOOL ret;
if (SMJobSubmit(kSMDomainSystemLaunchd, (CFDictionaryRef) plist, auth, (CFErrorRef *) error)) {
FMTLogDebug(#"Executed %#", command);
ret = YES;
} else {
FMTLogError(#"Failed to execute %# as priviledged process: %#", command, *error);
ret = NO;
}
// From whatever reason this did not work very well
// seems like it removed the job before it was executed
// SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
AuthorizationFree(auth, 0);
return ret;
} else {
FMTLogError(#"Unable to create authorization object");
return NO;
}
}
As for the restarting, this is usually done also using an external utility to which waits for a main application to finish and starts it again (by using PID). If you use sparkle framework you can reuse the existing one:
+ (void) relaunch {
NSString *relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:#"relaunch" ofType:#""];
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *pid = FMTStr(#"%d", [[NSProcessInfo processInfo] processIdentifier]);
[NSTask launchedTaskWithLaunchPath:relaunch arguments:#[path, pid]];
[NSApp terminate:self];
}
Another option is to hack the /Library/Application Support/com.apple.TCC/TCC.db sqlite database add the permissions manually using an auxiliary helper:
NSString *sqlite = #"/usr/bin/sqlite3";
NSString *sql = FMTStr(#"INSERT or REPLACE INTO access values ('kTCCServiceAccessibility', '%#', 1, 1, 1, NULL);", MY_BUNDLE_ID);
NSArray *args = #[#"/Library/Application Support/com.apple.TCC/TCC.db", sql];
NSTask *task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
[task waitUntilExit];
This however will disqualify the app from being app store. More over it is really just a hack and the db / schema can change any time. Some applications (e.g. Divvy.app used to do this) used this hack within the application installer post install script. This way prevents the dialog telling that an app is requesting to install an auxiliary tool.
Basically, MAS restrictions will require you to the route of having tge user turning on AX for all.
I found a potential solution on GitHub.
https://github.com/K8TIY/CW-Station
It has an auxiliary application which would be run at root to request access for the main application. It is a little outdated and is using some functions which have been deprecated so I am working on modernizing it. It looks like a good starting point.
Is there a way (some API) to get the list of installed apps on an iPhone device.
While searching for similar questions, I found some thing related to url registration, but I think there must be some API to do this, as I don't want to do any thing with the app, I just want the list.
No, apps are sandboxed and Apple-accepted APIs do not include anything that would let you do that.
You can, however, test whether a certain app is installed:
if the app is known to handle URLs of a certain type
by using [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"thisapp://foo"]
You can get a list of apps and URL schemes from here.
For jailbroken devices you can use next snipped of code:
-(void)appInstalledList
{
static NSString* const path = #"/private/var/mobile/Library/Caches/com.apple.mobile.installation.plist";
NSDictionary *cacheDict = nil;
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath: path isDirectory: &isDir] && !isDir)
{
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
NSDictionary *system = [cacheDict objectForKey: #"System"]; // First check all system (jailbroken) apps
for (NSString *key in system)
{
NSLog(#"%#",key);
}
NSDictionary *user = [cacheDict objectForKey: #"User"]; // Then all the user (App Store /var/mobile/Applications) apps
for (NSString *key in user)
{
NSLog(#"%#",key);
}
return;
}
NSLog(#"can not find installed app plist");
}
for non jailbroken device, we can use third party framework which is called "ihaspp", also its free and apple accepted. Also they given good documentation how to integrate and how to use. May be this would be helpful to you. Good luck!!
https://github.com/danielamitay/iHasApp
You could do this by using the following:
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector = NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSMutableArray *Allapps = [workspace performSelector:selectorALL];
NSLog(#"apps: %#", Allapps);
And then by accessing each element and splitting it you can get your app name, and even the Bundle Identifier, too.
Well, not sure if this was available back when the last answer was given or not (Prior to iOS 6)
Also this one is time intensive, yet simple:
Go into settings > Gen. >usage. The first category under usage at least right now is Storage.
It will show a partial list of apps. At the bottom of this partial list is a button that says "show all apps".
Tap that and you'll have to go through screen by screen, and take screenshots (Quick lock button and home button takes a screenshot).
I'm doing this now and I have hundreds of apps on my iPhone. So it's going to take me a while. But at least at the end of the process I'll have Images of all my apps.
I've grown tired of the built-in open Mac OS X command, mostly because it runs programs with your actual user ID instead of the effective user ID; this results in the fact sudo open Foo opens Foo with its associated application with your account instead of the root account, and it annoys me. So I decided to make some kind of replacement.
So far I've been successful: I can open any program under the open -a or open -b fashion, and support optionally waiting. I'm using NSTask for that purpose.
However, I'd like to be able to open documents too. As far as I can see, you need to use NSWorkspace for that, but using NSWorkspace to launch programs results in them being launched with your account's credentials instead of your command line program's credentials. Which is precisely what the default open tool does, and precisely what I don't want.
So, how can I have a program request that another program opens a document without using NSWorkspace? From the NSTask object, I can have the process ID, but that's about it.
Hopefully this will do the trick:
- (void)openFile:(NSString *)filePath withTask:(NSTask *)task {
int pid = [task processIdentifier];
NSAppleEventDescriptor *target = [NSAppleEventDescriptor descriptorWithDescriptorType:typeKernelProcessID bytes:&pid length:sizeof(pid)];
const char *urlUTF8 = [[[NSURL fileURLWithPath:filePath] absoluteString] UTF8String];
NSAppleEventDescriptor *urlDescriptor = [NSAppleEventDescriptor descriptorWithDescriptorType:typeFileURL bytes:urlUTF8 length:strlen(urlUTF8)];
NSAppleEventDescriptor *event = [NSAppleEventDescriptor appleEventWithEventClass:kEventParamAppleEvent eventID:kAEOpen targetDescriptor:target returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
[event setParamDescriptor:urlDescriptor forKeyword:keyDirectObject];
OSStatus err = AESendMessage([event aeDesc], NULL, kAENoReply | kAENeverInteract, kAEDefaultTimeout);
if (err != noErr) {
// Error handling goes here
}
// Activate the application
event = [NSAppleEventDescriptor appleEventWithEventClass:kAEMiscStandards eventID:kAEActivate targetDescriptor:target returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
err = AESendMessage([event aeDesc], NULL, kAENoReply | kAENeverInteract, kAEDefaultTimeout);
}
You may have to launch the application
using an NSTask and then send it the
appropriate open Apple Event.
Actually, can you launch using an
NSTask and then open the file via
NSWorkspace once you know it's
running? Or does that launch a new
instance of the application under your
user?
Original reply:
Does
open -a SomeApplication SomeFile
work?