Encapsulating and spawning another application and injecting parameters - objective-c

My issue is I have a child .app that I'd like to run after injecting some parameters. What want to do is run the app as the parent app (launching it sync and propagate focus/activation events to the child app).
The goal for me is to create an 'parent app' that launches another app, for example OtherApp.app. It should appear as if 'parent app' is OtherApp.app (i.e. not show up as a seperate application in the dock but the windows of OtherApp.app should be contained by 'parent app'). The reason I want to do this is so I can pass some initialization variables to OtherApp.app without modifying the .app itself.
Approaches I have taken
First approach is the simplest. Simply using system(#"VAR=VALUE /Applications/OtherApp.app"). However the issue with this is that the 'parent app' will instantly exit and OtherApp.app will open as a seperate application in the Dock.
Second approach: I've tried to do is use NSWorkspace with NSRunningApplication however that is not synchronous, the issue with this is that the 'parent app' will again instantly die:
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
NSRunningApplication* childApp = [[NSWorkspace sharedWorkspace]
openURL:[NSURL fileURLWithPath:#"/Applications/OtherApp.app"]
options:NSWorkspaceLaunchDefault|NSWorkspaceLaunchWithoutAddingToRecents
configuration:#{
NSWorkspaceLaunchConfigurationEnvironment: #{
#"VAR": #"VALUE"
}
} error:NULL];
}
Third approach is using Launch Services. However this is what my question is asking— I can't find any undeprecated API that lets me pass environment variables or "Launch Services Keys" (e.g. LSUIElement) nor can I find a way that lets me pass environment variables. This also instantly exits (I'm not to familiar with Launch Services' internals, perhaps someone can enlighten me?)
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
LSLaunchURLSpec launchSpec;
launchSpec.appURL = CFBridgingRetain([NSURL fileURLWithPath:#"/Applications/OtherApp.app"]);
launchSpec.asyncRefCon = NULL;
launchSpec.launchFlags = kLSLaunchDefaults;
launchSpec.passThruParams = NULL;
// Where can I specify environment vars or args?
return LSOpenFromURLSpec(&launchSpec, NULL);
}
Possible solutions
Create an NSApplication that communicates with OtherApp.app so 'parent app' doesn't exit immediately. Problems here are that, again, now they are two apps in the dock and also keeping focus in sync seems like it would be a more complex task.
Figure out how to pass environment variables to LS (Launch Services) APIs along with somehow being able to control the focus of the spawned app.
Somehow access the bundle and dynamically load the NSApplicationMain of the OtherApp.app though I typically can't use NSBundle with an executable (it throws an error saying so).
Right now #2 feels like the best bet though any assistance on alternative solutions would be greatly appreciated.

Related

Objective-C: Refreshing FrontmostApplication

I wrote this little program which is supposed to print the current frontmost application twice, with a 3-second break in between.
void printFrontmostApp() {
NSRunningApplication *frontmostApplication = [NSWorkspace sharedWorkspace].frontmostApplication;
NSLog(#"%#",frontmostApplication);
}
int main(int argc, const char * argv[]) {
#autoreleasepool {
printFrontmostApp();
sleep(3);
printFrontmostApp();
}
return 0;
}
When I ran this program, I realised that frontmostApplication is not refreshed when it is called the second time. I found the solution here. But I still have two questions:
1) I want to know why the frontmostApplication is not updated.
2) How do I force it to refresh every time I call it? I don't want to receive a notification every time the frontmost application deactivates, because it is a little inefficient for my purposes.
Edit:
Just to be crystal clear, let's suppose the time now is 10:00:00. I call printFrontmostApp, and it prints "Xcode" to the console, because Xcode is the current frontmost app. Then the program sleeps for 3 seconds. At 10:00:01, I opened another app, say TextEdit. At 10:00:03, my program calls printFrontmostApp for the second time. I expect it to print "TextEdit", which is the current frontmost application. But it prints "Xcode" instead. I can't understand this behaviour.
Can someone please explain what happens at 10:00:03? The function seems to "remember" the value of frontmostApplication at 10:00:00 and retains it when it is called the second time. I thought that any memory will be released once it goes out of scope, so why is this happening?
And how do I get my program to get the frontmost app at 10:00:03? I can get the frontmost app at 10:00:00, I should be able to do the same 3 seconds later right?
The documentation for -[NSWorkspace runningApplications] — not the method you're using, but related — says:
Similar to the NSRunningApplication class’s properties, this property will only change when the main run loop is run in a common mode. Instead of polling, use key-value observing to be notified of changes to this array property.
From the NSRunningApplication documentation:
Properties that vary over time are inherently race-prone. For example, a hidden app may unhide itself at any time. To ameliorate this, properties persist until the next turn of the main run loop in a common mode. For example, if you repeatedly poll an unhidden app for its hidden property without allowing the run loop to run, it will continue to return NO, even if the app hides, until the next turn of the run loop.
It's a near certainty that the same principle applies to -frontmostApplication even though the documentation doesn't say so explicitly. You will never get different results by polling without allowing the run loop to run.
For 1) the answer is the same described in the question you have linked: You have to observe this notification, that tells you when a new application was activated:
NSWorkspaceDidActivateApplicationNotification
About 2) You have different observers for activation and deactivation like:
NSWorkspaceDidDeactivateApplicationNotification
So your are not going to observe notifications that you are not registered, please take a look at NSWorkspace Notifications for a comprehensive list.
Otherwise, please define your question about refreshing/polling (that I think it's not a good idea anyways).

Sending requests to System Events from Objective-C/C?

Is there any way to convert the following applescript to Objective-C/C?
tell application "System Events" to set visible of process "Safari" to false
I know I could execute this applescript in Objective-C using the NSAppleScript class or calling system("osascript -e '...'"), however isn't there another way?
How does applescript do this?
Alternatively can I hide a window from another application from Objective-C/C?
Update:
I have found out that you can use SBApplication class to do this:
SBApplication *SystemEvents = [SBApplication applicationWithBundleIdentifier:#"com.apple.systemevents"];
/*SystemEventsApplicationProcess*/ id Safari = [[SystemEvents performSelector:#selector(applicationProcesses)] objectWithName:#"Safari"];
[Safari setVisible:NO]; // Doesn't work!
However this doesn't work as setVisible probably doesn't do what I think.
This is the class hierarchy of SystemEventsApplicationProcess:
SystemEventsApplicationProcess : SystemEventsProcess : SystemEventsUIElement : SystemEventsItem : SBObject : NSObject
And here are the methods available for these SystemEventsXXX classes:
SystemEventsApplicationProcess
applicationFile
SystemEventsProcess
setVisible:
visible
unixId
totalPartitionSize
shortName
partitionSpaceUsed
name
id
hasScriptingTerminology
setFrontmost:
frontmost
fileType
file
displayedName
creatorType
Classic
bundleIdentifier
backgroundOnly
architecture
acceptsRemoteEvents
acceptsHighLevelEvents
windows
menuBars
SystemEventsUIElement
select
clickAt:
setValue:
value
title
subrole
setSize:
size
setSelected:
selected
roleDescription
role
setPosition:
position
orientation
name
minimumValue
maximumValue
help
setFocused:
focused
entireContents
enabled
objectDescription
objectClass
accessibilityDescription
windows
valueIndicators
UIElements
toolBars
textFields
textAreas
tables
tabGroups
staticTexts
splitterGroups
splitters
sliders
sheets
scrollBars
scrollAreas
rows
relevanceIndicators
radioGroups
radioButtons
progressIndicators
popUpButtons
popOvers
outlines
menuItems
menuButtons
menuBarItems
menuBars
menus
lists
incrementors
images
growAreas
groups
drawers
comboBoxes
columns
colorWells
checkboxes
buttons
busyIndicators
browsers
attributes
actions
SystemEventsItem
setName:
name
id
removeActionFromUsingActionName:usingActionNumber:
pick
keyUp
keyDown
increment
editActionOfUsingActionName:usingActionNumber:
doScript
doFolderActionFolderActionCode:withItemList:withWindowSize:
decrement
confirm
cancel
attachedScripts
attachActionToUsing:
stop
start
saveAs:in:
moveTo:
exists
duplicateTo:withProperties:
delete
closeSaving:savingIn:
setProperties:
properties
objectClass
SBObject
// ...
NSObject
// ...
You can use NSRunningApplication, which represents (as its name implies) a running application, and has a -hide method.
NSWorkspace will give you a list of all the running apps: [[NSWorkspace sharedWorkspace] runningApplications], which you can filter, or you can get the object representing Safari using its bundle identifier: +[NSRunningApplication runningApplicationsWithBundleIdentifier:] (note that actually returns an array in case there are multiple running instances of the same app).
The code won't work unless you add the scripting bridge framework to your project and a couple other things. Have you done that... I can't tell. This link seems to have a good explanation of what is required if you need instructions.
By the way, "set visible" means hide the application just like if you hid it from the application menu. However if you want to hide an application I'm sure there's an NSWorkspace method.
Last bit of advice... for only a few lines of applescript code NSApplescript would be your best option. If you intend to use lots of applescript script code then the scripting bridge is the better choice, although I myself often just put a compiled script in my project and then use NSApplescript to initiate the handlers from that script. You can also use the ApplescriptObjC language too. You have lots of choices.

Simple utility to unhide mouse cursor in objective c console app

I'm trying to create a simple console app to unhide the cursor because a program I use has a bug and hides the cursor intermittently (Would rather write a quick util than wait for the bug to be fixed). I have added the AppKit framework and written the following simple console app (main.m):
#import <Foundation/Foundation.h>
#import <AppKit/NSCursor.h>
int main(int argc, const char * argv[])
{
#autoreleasepool {
[NSCursor unhide];
}
return 0;
}
but I get an EXC_BAD_ACCESS at the [NSCursor unhide] line. Any idea what I'm doing wrong?
I'm running this on Lion with XCode 4.3.2
I am also open to doing this with AppleScript, but I haven't been able to accomplish it there either.
Thanks!
What you're trying to do isn't going to work. Cursor objects are managed and owned by each application. You can't affect another application's cursor (without code injection).
You get an EXC_BAD_ACCESS because your program here doesn't actually have a cursor. One would be created during the usual GUI app start-up process, i.e., in NSApplicationMain(), if you had created a "Cocoa Application", but you would still only be able to affect your own application's cursor.
AppleScript seems like it would have a better chance of success, since it lets you execute some code that directly affects other apps, but I'm not at all sure that it has functionality to manage the cursor like this -- it seems a little too low-level for AS.

XCode: Two targets, two main files?

I'm working up a game in XCode, and I want to add a helper application that uses the main application's various classes to build the main application's data files.
Specifically:
The main application is a card game.
I want a helper app that uses the classes such as card.m while it creates the deck.xml files for the deck of custom cards the game uses.
My intuition is that the natural way to do this is to add a second compile target to the application, "Card Adder", to build this xml file. Unlike the main app, which is Cocoa Touch, I can make this one as a Shell application, to further make it quick & dirty.
Here's my hang up, and my question:
What kicks off as the main routine for this second target? Looking over my existing main.m file, I see it's only associated with the main game app. So far so good. But of course I can't add a second main.m file for the helper/shell app.
How do I get around this? What do I use in place of a main.m? How do I signal to the compile target "Card Adder" that this new file will contain it's main subroutine?
You may actually be able to have two main.m files. Put them in different folders on the file system. Add one to one target, and another to the other target.
It's not the name of the file, but the name/signature of the function
int main(int argc, char *argv[])
That matters. Just make a file with this function in it. You can have only one main per application.

Cocoa: int main function

I'm curious, what role does the int main function play in a Cocoa program? Virtually all of the sample code I've been looking at has only the following code in main.m:
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}
What exactly is this doing, and where does the program actually start stepping through commands? It seems my conceptions need readjustment.
Since a Cocoa project starts like any other, the entry point for the Operating system is main. However the Cocoa Architecture is constructed to actually start the processing of your program from NSApplicationMain, which is responsible for loading the initial window from your application and starting up the Events loop used to process GUI events.
Apple has a very in depth discussion on this under the Cocoa Fundamentals Guide : The Core Application Architecture on Mac OS X
If you want to learn how control passes from "launch this" to the main() function, the execve man page has the details. You would also want to read about dyld. main() is a part of the Unix standard. Every single program that you can run effectively has a main().
As others have mentioned, NSApplicationMain passes control to Cocoa. The documentation is quite specific as to what it does.
One interesting note, NSApplicationMain doesn't actually every return. That is, if you were to separate the call to NSApplicationMain from the return in your main function and put code in between, that code would never be executed.
main() is the entry point for your program.
When you run your program that is the first function called. Your program ends when you exit that function.
Also note that this does not come from Objective-C. This is simple C.
Have a look at
Wikipedia's page on it
Value returned from main is returned by the process to operating system when the process is done.
Shell stores the value returned by last process and you can get it back with $? :
> ls
a b c
> echo $?
0
> ls x
x: No such file or directory
> echo $?
1
ls is an application like anything else.
You can use the return value to chain multiple processes together using shell script or anything else that can execute a process and check for return value.
I'm wondering where the code begins
executing (like why does an NSView
subclass execute and draw without me
explicitly calling it?) and if I'm not
supposed to stick my main loop in int
main() where does it go?
In an xcode project you have a main.m file that contains the 'int main' function. You won't actually find the code that calls the NSView draw explicitly, this code is hidden deep within an iPhone or Mac OS X framework. Just know that there is an event loop hidden deep within your 'int main' that checks for changes so that it knows when to update your view. You don't need to know where this event loop is, it's not useful information since you can override methods or create and assign delegates that can do things when this happens.
To get a better answer, you'll need to explain what you mean by a 'main loop' that you wanted to put inside the 'int main' function.
It's just weird to me coming off a
little experience in C++. It looks
unnatural that the main function would
be so empty.
You can encapsulate a billion lines of code into one function and put it into 'int main'. Don't be deceived by a main only having a few lines, that is done on purpose. Good programming teaches us to keep code in specific containers so that it is well organized. Apple chose to make the "real" launch point of their iPhone apps in this single line of code inside the main.m file:
int retVal = UIApplicationMain(argc, argv, nil, #"SillyAppDelegate");
From that one piece of code, an app's delegate is launched and won't return control to the main function until it is done.