Single Instance Application- Activate Window - Cocoa - objective-c

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

Related

Relaunch OS X app to execute CGEventTapCreate() when AXIsProcessTrusted() is true

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]];

Cocoa: Can I prevent duplicated launching of one same application?

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.

Prevent iCloud window from opening on OSX 10.8 app launch

I have written an OSX app that uses iCloud document storage. Whenever I open it in Mountain Lion (not on Lion), an iCloud window opens that looks like the following:
Is there a way to prevent this from happening on launch?
Updates:
1) applicationShouldOpenUntitledFile: is not getting called (yes, I'm sure I'm listening in my delegate.
2) If I force quit the app, the next time it opens, I don't get the dialog. But, if I go through the normal Quit process, it does appear.
Update 2 (also added as an answer, to help people that may stumble across this question in the future):
The applicationShouldOpenUntitledFile: from the duplicate question was not working. After lots of experimentation, I figured out that if I remove the NSDocumentClass key and value from my Info.plist in the CFBundleDocumentTypes array, the window is no longer opened. I've added that answer to the duplicate question as well.
Putting below codes in your App Delegate lets you bypass that iCloud pop up New Document screen. Tested for High Sierra.
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
// Schedule "Checking whether document exists." into next UI Loop.
// Because document is not restored yet.
// So we don't know what do we have to create new one.
// Opened document can be identified here. (double click document file)
NSInvocationOperation* op = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(openNewDocumentIfNeeded) object:nil];
[[NSOperationQueue mainQueue] addOperation: op];
}
-(void)openNewDocumentIfNeeded
{
NSUInteger documentCount = [[[NSDocumentController sharedDocumentController] documents]count];
// Open an untitled document what if there is no document. (restored, opened).
if(documentCount == 0){
[[NSDocumentController sharedDocumentController]openUntitledDocumentAndDisplay:YES error: nil];
}
}
The applicationShouldOpenUntitledFile: from iCloud enabled - Stop the open file displaying on application launch? was not working. After lots of experimentation, I figured out that if I remove the NSDocumentClass key and value from my Info.plist in the CFBundleDocumentTypes array, the window is no longer opened.

Window Constantly Wants To Be On Top Of Others - Xcode

My app has buttons that open automator workflows like this:
- (IBAction)actionname:(id)sender {
NSTaskname = [[NSTask alloc] init];
[NSTaskname setLaunchPath:#"/usr/bin/automator"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects:#"/Applications/appname.app/Contents/Resources/workflowname.workflow", nil];
[NSTaskname setArguments:arguments];
[NSTaskname launch];
}
The only problem is, that every single one appears behind the window of my app. Also, one workflow launches another app which also appears behind the window.
How can I fix this?
You can probably use NSRunningApplication to bring your NSTask process to the front with its PID like this...
NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:[NSTaskname processIdentifier]];
[app activateWithOptions: NSApplicationActivateAllWindows];
And if you need to activate a specific application, for example your workflow that launches another app, then you could do this using the application's bundle identifier. This example will activate Safari.
NSArray* apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.Safari"];
[(NSRunningApplication*)[apps objectAtIndex:0] activateWithOptions: NSApplicationActivateAllWindows];

Skype Mac API - Use AppleScript or 5 year old API?

I have a x86_64 app that I would like to have optionally read Skype status messages. However, the 5 year old skype mac framework is 32-bit, and if there is a way to have that compile within a 64-bit app, I haven't found it.
My question is, basically, how should I go about doing this? I really only need to get and set the USERSTATUS AWAY/ONLINE string.
Using AppleScript, a "Should Skype allow this" dialog pops up... every time. This is highly inefficient and downright irritating.
Advice?
I'm considering writing a 32-bit CLI wrapper, but that seems like overkill.
I used Notification Watcher to learn that Skype's API just works with NSDistributedNotifications. Repeating those notifications worked like a charm for a 64bit app.
Check out Scripting Bridge: Introduction to Scripting Bridge Programming Guide for Cocoa
If I remember right, the permission dialog does not come up once you allow permission.
I my Skype Apple Scripts I have to GUI to click them. If they come up.
tell application "Skype" to launch
delay 15
(* this part if the security API window comes up*)
tell application "System Events"
tell application process "Skype"
if exists (radio button "Allow this application to use Skype" of radio group 1 of window "Skype API Security") then
click
delay 0.5
click button "OK" of window "Skype API Security"
end if
end tell
end tell
delay 5
I've found out that if you open "Skype.app" by viewing bundle contents -> Frameworks you'll find a 64bit and 32bit skype.framework
This is an answer in reply to a request from twitter. I used this code after asking this question way back when. I have not needed to look into the Skype API since this works just fine, but I imagine that its been updated since I last tried to use it. Anyhow...
Here's a list of the NSDistributedNotifications that I use when communicating to skype:
SKSkypeAPINotification
SKSkypeAttachResponse
SKSkypeBecameAvailable
SKAvailabilityUpdate
SKSkypeWillQuit
Just like any other kind of NSDistributedNotification, you simply register and process the results:
[[NSDistributedNotificationCenter defaultCenter]
addObserver:self selector:#selector(setStatusAfterQuit:)
name:#"SKSkypeWillQuit"
object:nil
suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
These are the iVars that I keep to sync with Skype:
NSString *applicationName;
NSString *mostRecentStatus;
NSString *mostRecentStatusMessage;
NSString *mostRecentUsername;
int APIClientID;
BOOL isConnected;
BOOL needToSetMessage;
NSString *nextMessage;
NSString *nextStatus;
Here's an example of how to connect to skype:
-(void) skypeConnect{
if (!isConnected){
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"SKSkypeAPIAvailabilityRequest"
object:nil
userInfo:nil
deliverImmediately:YES];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"SKSkypeAPIAttachRequest"
object:applicationName
userInfo:nil
deliverImmediately:YES];
}
}
Here's an example of getting the status message (after you've registered with Skype):
-(void) processNotification:(NSNotification *) note{
if ([[note name] isEqualToString:#"SKSkypeAttachResponse"]){
if([[[note userInfo] objectForKey:#"SKYPE_API_ATTACH_RESPONSE"] intValue] == 0){
NSLog(#"Failed to connect to Skype.");
isConnected = NO;
}else {
NSLog(#"Connected to Skype.");
APIClientID = [[[note userInfo] objectForKey:#"SKYPE_API_ATTACH_RESPONSE"] intValue];
isConnected = YES;
[self sendCommand:#"GET PROFILE MOOD_TEXT"];
if (needToSetMessage){
[self sendCommand:[NSString stringWithFormat:#"SET USERSTATUS %#",nextStatus]];
[self sendCommand:[NSString stringWithFormat:#"SET PROFILE MOOD_TEXT %#",nextMessage]];
needToSetMessage = NO;
nextMessage = #"";
nextStatus = #"";
}
}
}
}