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.
Related
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]);
}
I have two cocoa apps. Application1 calls Application2(abc.app) as below-
if ([[NSWorkspace sharedWorkspace] respondsToSelector:#selector(launchApplicationAtURL:options:configuration:error:)])
return nil != [[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:#"abc.app" isDirectory:NO] options:NSWorkspaceLaunchDefault configuration:nil error:NULL];
This should open Application2 (abc.app). Now If application 1 calls application 2 again, I want to activate abc.app (If this is minimised in the dock).I want to ensure there is single instance of abc.app running. How can we achieve this?
Not quite sure of your problem. Mac OS X by default only launches one instance of an app. (Unless you have several physical copies of the executable on disk, but even for that case there's an Info.plist key that prohibits launching an app if one with the same bundle ID is already running).
Also, by default, NSWorkspace should bring to front and un-collapse your application if it has no other windows open (it should behave as if you'd double-clicked it again in Finder, or clicked its dock icon when it's already running), and it will call the second app's 'reopen application' handler.
If it doesn't do it, you could try to explicitly un-collapse your main window from the 'reopen' delegate method, or if you don't want this to happen generally (but why wouldn't you?), you could look into sending an Apple Event between the two applications.
Also, you can check if the second application is already running by looking at the runningApplications and looking for an entry with the same bundle ID.
You can check if your second application is running and check if it's the active application (frontmost) with NSRunningApplicationclass.
// check if abc.app is running
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.youApplication.abc"];
if ([apps count] == 0)
{
// not running, launch it
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:#"abc.app" isDirectory:NO] options:NSWorkspaceLaunchDefault configuration:nil error:NULL];
}
// check if abc.app is frontmost
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.youApplication.abc"];
if ([apps count])
{
// abc.app is running, check if active
if (![(NSRunningApplication*)[apps objectAtIndex:0] isActive])
{
// not active, activate it
[(NSRunningApplication*)[apps objectAtIndex:0] activateWithOptions: NSApplicationActivateAllWindows];
}
}
You canĀ“t from App1. You can check if your App2 is launched from App2 with Notifications like here
If I am getting a CGWindowID (_windowID) as follows...
NSArray *windowList = (__bridge NSArray *)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
for (NSDictionary *info in windowList) {
if ([[info objectForKey:(NSString *)kCGWindowOwnerName] isEqualToString:#"window name"] && ![[info objectForKey:(NSString *)kCGWindowName] isEqualToString:#""]) {
_windowID = [[info objectForKey:(NSString *)kCGWindowNumber] unsignedIntValue];
}
}
How do I properly test the window id is still valid and the window has not been closed? Do i just run similar code just checking window id exists?
Thanks in advance
The documentation for the kCGWindowListOptionOnScreenOnly constant says:
List all windows that are currently onscreen. Windows are returned in
order from front to back. When retrieving a list with this option, the
relativeToWindow parameter should be set to kCGNullWindowID.
So the windows would certainly be on screen, since nothing appears to be happening between the call to CGWindowListCopyWindowInfo and your action on it.
Maybe you'd want to test to make sure they are not hidden or minimized?
I have an app that watches a folder for incoming jobs and then processes them. A job consists of a Folder with several job files inside the folder. Jobs are usually copied over the internet so when a folder is added to my Watched Folder I'm having my app get the attributes for the files inside the job folder, wait 20 seconds, and compare the current attributes for NSFileSize to see if there have been any changes. When everything matches, and no changes are detected it can pass the job folder along to be processed. This is the code I have:
while (fileSizes == NO && fileCount == NO) {
NSLog(#"going for a loop");
NSArray *jobFiles = [fm subpathsAtPath:jobPath];
NSMutableArray *jobFileAttrs = [[NSMutableArray alloc] init];
int i = 0;
while (i < [jobFiles count]) {
NSString *filePath = [jobPath stringByAppendingPathComponent:[jobFiles objectAtIndex:i]];
[jobFileAttrs addObject:[fm attributesOfItemAtPath:filePath error:nil]];
++i;
}
sleep(20);
NSArray *jobFiles2 = [fm subpathsAtPath:jobPath];
NSMutableArray *jobFileAttrs2 = [[NSMutableArray alloc] init];
i = 0;
while (i < [jobFiles2 count]) {
NSString *filePath = [jobPath stringByAppendingPathComponent:[jobFiles2 objectAtIndex:i]];
[jobFileAttrs2 addObject:[fm attributesOfItemAtPath:filePath error:nil]];
++i;
}
if ([jobFiles count] == [jobFiles2 count]) {
i = 0;
fileSizes = YES;
while (i < [jobFiles count]) {
NSLog(#"Does %ul = %ul", [[jobFileAttrs objectAtIndex:i] objectForKey:NSFileSize], [[jobFileAttrs2 objectAtIndex:i] objectForKey:NSFileSize]);
if ([[jobFileAttrs objectAtIndex:i] objectForKey:NSFileSize] != [[jobFileAttrs2 objectAtIndex:i] objectForKey:NSFileSize]){
fileSizes = NO;
}
++i;
}
if (fileSizes)
fileCount = YES;
}
This code works as intended in Lion, but when I run the App on Snow Leopard I get inconsistent values for the NSFileSize attribute. Every time the loop runs I get a completely different set of value than even the previous loop. This is obviously for a folder full of files that is no longer being copied and should give matching values for the file sizes.
Why doesn't this work in Snow Leopard, and what do I need to do to fix this? Part of my problem is I'm only set up for development on a Lion Machine, so I have to make build, and then transfer it to a snow leopard machine with out a debugger to test. It's making it hard for me to trouble shoot.
There are a few problems with this but for one thing, you are comparing the value of two objects using a boolean operator (!=). This is comparing the pointer locations of the two objects, not their value.
To compare two objects, you must use the isEqual: method:
if (![[[jobFileAttrs objectAtIndex:i] objectForKey:NSFileSize] isEqual:[[jobFileAttrs2 objectAtIndex:i] objectForKey:NSFileSize]])
{
fileSizes = NO;
}
Secondly, this is fundamentally bad design. What your code is doing is called polling, and the presence of sleep() in the code is a bad sign. You should never use sleep in Cocoa code, especially if your code is executing on the main thread as it will block the main run loop.
If you absolutely must use polling, you should use an NSTimer object.
However, in this particular case you don't need to use polling to determine when there has been a change to the folder contents, you can use the FSEvents API instead. The API is C-based and a bit obtuse so you might want to use Stu Connolly's SCEvents Objective-C wrapper.
What you should be doing is maintaining an array of the current files and their file sizes (probably as an NSArray instance variable containing dictionaries with keys for file name and file attributes) and then when you are notified of a change on disk, get the current status of the files and compare that with the information in your stored array. You would then replace your stored array with the updated file information.
The other file monitoring option is kqueues. These differ from FSEvents in that they are specific to a particular file, whereas FSEvents monitors directories, not individual files. In your case, kqueues may actually be more appropriate. Uli Kusterer has written a great Objective-C wrapper for kqueues. If you use this, you just need to start monitoring a particular file and you'll be notified whenever it changes via a delegate. This would be a simpler option if you only need to check one file at a time.
I hope this makes sense.
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.