How to launch Apps, from my App, with a custom parameter so I can check whether the app was launched by me? - objective-c

I'm working on this app that launches other apps. I'm listening to app launches using:
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(appLaunched:) name:NSWorkspaceDidLaunchApplicationNotification
object:nil];
And I launch them using (Mail is just an example):
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:#"lalalala"], NSWorkspaceLaunchConfigurationArguments, nil];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL URLWithString:#"/Applications/Mail.app"] options:NSWorkspaceLaunchWithoutActivation configuration:dict error:nil];
I did some research, and I saw that you can send an argument when you launch an app (that's why I used the var dict in the code above), but I'm having an issue with this: even using NSWorkspaceLaunchWithoutActivation, the Mail.app is launched and becomes focused with a new composing window. I don't know why it's doing that.
Another thing, if I manage to successfully send a custom argument without focusing the app, how can I check if the app was launched by me (check if the argument is there)?
PS: I'm looking for App Store-ready methods.

Send the timestamp (UTC) together with the app name you started to your server or a local file if possible.
Then you can track it.

Firstly, I'd try NSWorkspaceLaunchAndHide if NSWorkspaceLaunchWithoutActivation isn't "working". Not ideal, no.. but a kludge...
Secondly... here's a "full, running example" that does the trick..
#import <Cocoa/Cocoa.h>
NSString *psAUX(NSString*grep) {
FILE *read_f; char buff[BUFSIZ+1]; int char_rd; NSString *res, *cmnd;
memset(buff, '\0', sizeof(buff));
cmnd = [NSString stringWithFormat:#"/bin/ps aux|grep -i %#",grep];
read_f = popen(cmnd.UTF8String, "r");
if (read_f == NULL) return nil;
char_rd = fread(buff, sizeof(char), BUFSIZ, read_f);
if (!char_rd) return nil;
return res = [NSString stringWithUTF8String:buff], pclose(read_f), res;
}
int main(int argc, char *argv[]) { #autoreleasepool {
NSString* secretStr; NSURL *mailURL; NSDictionary *cfg; NSWorkspace *ws; NSApplication.sharedApplication;
secretStr = #"TAMPAX";
mailURL = [NSURL URLWithString:#"file:///Applications/Mail.app"];
cfg = #{NSWorkspaceLaunchConfigurationArguments:#[secretStr]};
ws = NSWorkspace.sharedWorkspace;
[ws launchApplicationAtURL:mailURL options:0 configuration:cfg error:nil];
fprintf(stderr,"%s",
[psAUX(#"Mail.app") containsString:secretStr]
? "You ARE Mail's baby's daddy!"
: "Hands off, she's NOT yours!");
[NSApp run]; } }
NSLog -> You ARE Mail's baby's daddy!
Congratulations!

You can create a new Task using NSTask. With NSTask you can set arguments as well as some environment variables to app so that you can check if it is launched by you or by someone else.
Here is the sample code sniffet to do so:
NSTask* taskApp = [[NSTask alloc] init];
[taskApp setLaunchPath:#"App path goes here"];
[taskApp setArguments:[NSArray arrayWithObjects:#"Arg1",#"arg2", nil]];
[taskApp setEnvironment: [[NSProcessInfo processInfo] environment]];
[taskApp launch];

Related

Run a shell command on Objective c and simultaneously get output

Let's say I want to run curl -o http://example.com/file.zip via an Objective C app and I want to have a label or text box containing the download status which gets updated while the command is running. Maybe this could be achieved using dispatch_async, but now sure how. Before marking as duplicate, the methods I found, run the command, and after it has finished you get the output. I want to get the output while it's running, kinda like a terminal emulator.
You need to connect a NSPipe to the NSTask using the standardOutput property and register to receive Data Available notifications.
#interface TaskMonitor: NSObject
#property NSPipe *outputPipe;
#end
#implementation TaskMonitor
-(void)captureStandardOutput:(NSTask *)process {
self.outputPipe = [NSPipe new];
process.standardOutput = self.outputPipe;
//listen for data available
[self.outputPipe.fileHandleForReading waitForDataInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification object:self.outputPipe.fileHandleForReading queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSData *output = self.outputPipe.fileHandleForReading.availableData;
NSString *outputString = [[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
// do something with the string chunk that has been received
NSLog(#"-> %#",outputString);
});
//listen again...
[self.outputPipe.fileHandleForReading waitForDataInBackgroundAndNotify];
}];
}
#end

JavascriptCore hangs when running a JS with callback

I am trying to run a JS function using JavascriptCore from Objective-C. Here's the JS code:
var FeedParser = require('feedparser');
var run = function(data, options){
var feedparser = new FeedParser(options);
feedparser.on('readable', function() {
if(callback){
callback(this.read());
}
});
feedparser.end(data);
};
module.exports = {
run: run
};
And here's the Objective-C code that invokes the JS:
NSString *path = [[NSBundle mainBundle] pathForResource:#"rss" ofType:#"js"];
NSStringEncoding encoding;
NSError *error = nil;
NSString *js = [NSString stringWithContentsOfFile:path
usedEncoding:&encoding
error:&error];
JSContext *context = [[JSContext alloc] init];
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(#"%#", value);
}];
context[#"callback"] = ^(JSValue *val){
#try{
if([val isString]){
NSLog(#"%#", [val toString]);
} else if([val toDictionary][#"0"]){
NSLog(#"%#", [val toArray]);
} else {
NSLog(#"%#", [val toDictionary]);
}
}
#catch(NSException *e){
}
};
[context evaluateScript:js];
JSValue *parse = context[#"rss"][#"run"];
[parse callWithArguments:#[data, o]];
Note:
Just to be clear, this approach works fine for all other JS functions that immediately return results. I am using browserify to package them all into a single JS. I'm just noticing problems with functions that return asynchronously via callback. (Notice I'm adding callback function to the context.
When I run this on XCode it hangs (not just the app but also the XCode editor itself, and it never recovers until I kill the process from Activity Monitor).
However for some reason it returns the result as soon as I unplug the iPhone from my laptop. (At this point XCode is still in a limbo state until I kill it)
Once detached from XCode, the app runs fine. It just hangs while it's running from XCode invoked run.
Probably you are trying to convert some JavaScript value to NSDictionary via toDictionary call. This method attempts to convert value recursively, so it leads to a hung often. I suspect that any JS value with circular references causes infinite recursion in this case. Try to work with JSValue directly instead.

Is it possible to send a text message from my Mac programmatically?

Since Apple has shut down their developer portal pending security upgrades they've created a status page so we can monitor what features they've brought back online. I've written a simple program to monitor this status page for changes.
My Mac is set up to receive iMessages sent to my iPhone. I'm wondering if anyone knows if its possible to have the program I've written send an iMessage to my iPhone when there's been a change in the status page Apple has up.
I usually develop for iPhone, so I appreciate any insight people can offer. The simple program I've written below checks every fifteen minutes if there's been an update and brings the update page up on Safari if there has been.
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
NSDateFormatter *format = [NSDateFormatter new];
[format setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:8*3600]];
[format setDateFormat:#"YYYY-MM-DD HH:mm:ss"];
NSString *text = #"";
bool no_connection;
do {
text = [[NSString alloc]initWithContentsOfURL:[NSURL URLWithString:#"https://developer.apple.com/support/system-status/"] encoding:NSASCIIStringEncoding error:NULL]; // pulls the source html from Apple's update page
NSString *status = #"No change";
if ([text rangeOfString:[self currentStatus]].location == NSNotFound) { // if cannot find the old text, then there has been a change
status = #"Update!";
}
no_connection = text == nil || [text length] == 0; // if no text or nil text then connection issue
if (no_connection) {
status = #"error making connection";
}
NSLog(#"status: %#",status); // report on status
if (no_connection) { // if no connection then try again in a minute
sleep(60);
continue;
}
sleep(900); // wait 15 minutes (60 x 15 = 900) and check again
} while ([text rangeOfString:[self currentStatus]].location != NSNotFound); // continue checking until there has been a change
NSURL *url = [NSURL URLWithString:#"https://developer.apple.com/support/system-status/"]; // bring up the update page in the browser
if( ![[NSWorkspace sharedWorkspace] openURL:url] )
NSLog(#"Failed to open url: %#",[url description]);
}
-(NSString*)currentStatus { /* returns the specific text in the html source that I'm checking for a change
"<span>" will be replaced with a hyperlink tag */
return #"<span>Certificates, Identifiers & Profiles";
}
#end
I tried to do this once and never came up with an Objective-C solution. However, you could have your app run AppleScript. Here's how you'd do it from the command line:
osascript -e 'tell application "Messages" to send "Test Message" to buddy "MyBuddy"'
And the answer here describes how to run AppleScript from Objective C:
Run AppleScript from Cocoa Application

NSWorkspace vs NSTask to start iTunes from a sandboxed app

I'm trying to run iTunes from my ObjectiveC app that runs in a sandbox.
Apple documentation mentions that 'child processes created with the NSTask class inherit the sandbox of the parent app'. The result is that when running iTunes, some permission error pops up and iTunes is closed.
When running it using NSWorkspace methods it does not crash and seems it's running outside any sandbox. Does that mean that i have permission to insert some dynamic library at launch time using DYLD_INSERT_LIBRARIES ?
Here's some code:
NSString* appPath = #"/Applications/iTunes.app";
// Get application URL
NSBundle *targetBundle = [NSBundle bundleWithPath:appPath];
NSURL *applicationURL = [targetBundle executableURL];
NSString* libPath = [NSHomeDirectory() stringByAppendingPathComponent:#"myLib.dylib"];
// Environment setup
NSDictionary *config = nil;
NSDictionary *env = [NSDictionary dictionaryWithObject:libPath forKey:#"DYLD_INSERT_LIBRARIES"];
NSNumber *arch = [NSNumber numberWithInt:(int)NSBundleExecutableArchitectureI386];
config = [[NSDictionary alloc] initWithObjectsAndKeys:env, NSWorkspaceLaunchConfigurationEnvironment,
arch, NSWorkspaceLaunchConfigurationArchitecture, nil];
// Launch application
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:applicationURL
options:0
configuration:config
error:nil];
[config release];
When the above code runs in a sandbox iTunes starts without any lib. Any suggestion?
Thanks,
Vlad.

NSTask does not receive stdout outside of Xcode 4.3.3

this is my first post, so let me send me many thanks to all the posting guys
outside there (I use SO extensively passively - great!)
I'm working on an video exporting tool for Mac OS X using the good old Quicktime API.
Brief:
I cut frames from multiple input movies an arrange them (scaled) to a new output
movie (kind of media-kiosk).
As many of the needed QT functionality (e.g. writing timecode ...) need to be
nested in a 32-bit Application, I decided to do this offline using a 32 bit command line
tool. The tool renders frame by frame (offline) and prints the current progress
in values between 0.0 and 1.0
It is invoked by the main application (Cocoa, GUI, the modern stuff) via
NSTask. The stout is caught by a NSPipe.
I took a look at some examples and 'll give you quick overview over my code:
NSTask *task;
NSPipe *pipe;
float progress;
// prepare the offline process
//
//
-(void) prepareOfflineExport {
task = [[NSTask alloc] init];
pipe = [[NSPipe alloc] init];
[task setLaunchPath:pathToRenderer];
[task setStandardOutput:pipe];
}
// arguments are passed outside
// invoke the process
//
-(void) startOfflineExport {
progress = 0.0f;
NSArray *argv = [NSArray arrayWithObjects: /* command line args */, nil];
[task setArguments:argv];
NSFileHandle *fh = [pipe fileHandleForReading];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataReady:) name:NSFileHandleReadCompletionNotification object:fh];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskTerminated:) name:NSTaskDidTerminateNotification object:task];
[task launch];
[fh readInBackgroundAndNotify];
}
// called when data ready
//
//
-(void) dataReady:(NSNotification*)n {
NSData *d = [[n userInfo] valueForKey:NSFileHandleNotificationData];
if([d length]) {
NSString *s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
progress = [s floatValue];
}
}
// called when process exits
//
//
-(void) taskTerminated:(NSNotification*)n {
task = nil;
progress = 1.0f;
}
Now the Problem:
When launching the application inside Xcode (via "run"), everything works fine.
The Invocation is done proper, the process is visible in the activity monitor and
the NSLevelIndicator (on the guy of the innovating app) is proceeding well according
the (float) progress variable.
BUT: if i "archive" the application and execute it outside of Xcode, the stdout of my
Command Line Tool never seem to reach the application. I tried writing a debug file
in
-(void) dataReady:(NSNotification*)n
No chance, it is never called! I tested the issue on several Macs, same problem...
Did I make an obvious mistake or is there some preferences to configure (Sandboxing is off),
maybe known issues that I overlooked?
Thank you for help
Greetings
Mat