Creating a conditionally headless app?

Is there a way in ASOC (or even pure cocoa-based objective-c) to create a conditionally headless app?

By this, I mean that if the user double clicks on the app itself, the menu bar gets created for controlling the program, the run loop gets started, etc. However, if the user opens another file using this app, it runs completely headless (no menu bar, no windows, no run loop, no *.xib file, etc.).

In the “headless” case, I’d like to avoid reading the *.xib, starting a run loop, and all other “headful” overhead.

Thanks in advance.

You can certainly have no windows, no visible menu bar, and I think, no xib loaded, but if there’s no run loop what do you want the program to do?

Ric

I’ve used a couple of flags along these lines:

-- Handle file opening at startup
	property fileOpened : false --  application_openFile_ sets to true (it never goes back to false) this flag is only checked in applicationDidFinishLaunching_
	property appIsLaunchingWindowIsInvisible : false -- true between applicationWillFinishLaunching_ and applicationDidFinishLaunching_ (when the app is opening double clicked files)

along with a test in applicationDidFinishLaunching:

on applicationDidFinishLaunching_(aNotification)
--log theDiskStateData
		if my fileOpened is equal to true then
			--say "you opened a file"
			tell me to quit -- this appears to work fairly harmlessly
			return {}
		end if
...

To implement the behavior you want. So far it’s worked nicely.

I have an app that if doubled clicked it will launch as normal.
But it can also be run from the command line with no GUI. Done in Objective-c.

I do this by checking the argc and argv in the main.m file.

int main (int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
		 
	//-- make sure there is a argument at the argv[1] index. or an error will be thrown when getting the stringWithCString.
	NSString *ARG =@"noShellArg";// if no argument is given in the shell.
	
	
	
	if (argc > 1 ) { 
		 
	
		ARG =  [NSString stringWithCString:argv[1] encoding:1]  ;// there is an argument so get it.
	}

	//-- -psn" is in the argv  returned if app is double clicked. Ranged used to check. look for -psn in ARG
	NSRange aRange = [ARG rangeOfString : @"-psn"];
	
	
		//-- this is the correct argument that should be in the shell command.
	NSString *runShell =@"-runshell";
	
		 					 
		
	
		//--Determine if the app is launched from the command line or app itself is opened.
	
	 
	if ([ARG isEqualToString]) {//-- if argv[1] matched then run from CLI.
		
			//--   Put your code here
		
		return 0;
		
	} else if  (aRange.location != NSNotFound) {//-- range was found. argv[1] matches the range, so.. 
												//--app was doubled click, (Opened)
		
		return NSApplicationMain(argc,  (const char **) argv);
		
	} else 
{
		//---an incorrect argument was given in the command line.
	NSString *wrongShell1 =@"Argv[1] error:Wrong argument or no argument given.";
	NSString *wrongShell2 =@"Use the \"-runshell\" option to run MyApp from the command line.";
	printf("%s\n%s\n", [wrongShell1 UTF8String],[wrongShell2 UTF8String]);
		return 0;
		}
	
	
	[pool drain];
}

But my app does not open files it writes them out.

How would your app open the files if it is not running . Drag and drop??

Thank you very much. I now have an idea of how I might be able to do what I want.

Yes, I want the app to run in a headless manner when I drag and drop, and also when I open a file that is associated with that app (via double-click in the Finder or via the open command). And I want it to run in a “headful” manner only if I directly open the app, itself.

So now, the question that remains for me is this: I now see how to use the -psn argument to decide whether or not an app was opened via the command line. But how can I distinguish between the case where the app, itself, was opened directly in the Finder (in which case I want it to run in a “headful” manner), and the case where something was dragged and dropped on the app (in which case I want it to run headless and be aware of the name of the item dropped on it)?

I’m now going to investigate this specific question, but any suggestions or pointers to docs are more than welcome.

When there are no windows, no menu bar, and no xib (i.e., when the app is running headless), I want to use my own logic (different from the default MacOSX logic) to decide which app to use to launch a given file. Once I decide on this app, I will then use [[NSWorkspace sharedWorkspace] openFile:file withApplication:app] to open the file.

Thank you. So if I understand correctly, it looks like you force your application to quit during applicationDidFinishLaunching_, if the given file has already been opened. This brings up a few more questions:

  1. Won’t windows be open and the xib processed, etc., before applicationDidFinishLaunching_ is called?

  2. If I quit at that point, won’t that stop whatever further processing I want to make use of? If so, does that mean that I have to perform all of my headless processing within application_openFile_, and that applicationDidFinishLaunching_ won’t even be called until after application_openFile_ exits?

  3. Are there analogs to the fileOpened and appIsLaunchingWindowIsInvisible properties within the objective c world that I can access via a cocoa-based ASOC application?

Thanks.

Not if you uncheck “Visible at Launch” in the xib. Then explicitly show it when app is done launching:

set mainWindow's isVisible to true 

Yes, between applicationWillFinishLaunching_ and applicationDidFinishLaunching_ .

NSApplicationDelegate’s application:openFile: works fine in ASOC. Something like this works:

on application_openFile_(theApplication, filepath)
		--NSApplicationDelegate Protocol Reference
		--log "XXXXXXXXXXXX    " & filepath as text  -- log doesn't work inside this func
		set my fileOpened to true -- used in applicationDidFinishLaunching_
		set filepathtext to filepath as text -- COERCE !!!!!
		display dialog filepath as text   -- COERCE !!!!!  -- so use a dialog if I want to see it
		set theDict to loadAFile_(filepathtext)  -- Get the file into the App
		return yes
		
	end application_openFile_


	on loadAFile_(pathtext)
		--Files are straight plists, so I can load them directly into a dictionary.
		set theDict to current application's NSDictionary's dictionaryWithContentsOfFile_(pathtext) -- works
		-- "On Error" wont catch problems, but an error returns 'missing value' in theDict
		-- I'll leave the calling func to deal with that
	end loadAFile_

Window visibility is dealt with in answer to first question.

First of all, thank you to patron22 for the further clarification.

And in response to my own question about how to tell in main.m whether an app was invoked by double-clicking versus dragging-and-dropping something onto it …

It looks like there is no way, at least solely via the argc/argv values, to distinguish between those two cases. In both instances, there is a -psn_0_XXXXXX argument, where 0 and XXXXXX appear to represent the high and low parts of a ProcessSerialNumber.

So this begs a further question: is there a way, using this PSN, to query the process to see whether or not it was invoked via a direct double click or whether something was dragged and dropped onto it?

I am now engaging in an investigation of that question, and again, any suggestions and pointers to docs would be greatly appreciated.

Thanks again, in advance.

The XIB will be processed in the moment when NSApplicationMain() is called and the Main Nib file is specified in Info.plist.

If you want to run the executable without run loop, you must make the decision before calling NSApplicationMain()
But I doubt that ASOC classes can be executed without a run loop.

All NSApplication event handlers (like applicationDidFinishLaunching) are not called without a run loop

Thanks, StefanK.

So in main.m, I want to examine the state of my application and apply the following logic:

if application is invoked by double-clicking on the app itself …
call NSApplicationMain
else if application is invoked by dragging-and-dropping a file onto it …
find out the file’s full path name
process the file in an application-specific manner
do not call NSApplicationMain
else
(this means that the program is being invoked from the command line)
print a usage message and exit
end if

I don’t yet know how to distinguish between the first two cases, since for both of them, the -psn_0_XXXXXX parameter is passed, and nothing else.

… but I’m still investigating.

I guess, drag & drop requires a run loop too.
Note that plain AppleScript droplets are actually Cocoa applets with an open event handler.

I recommend not to reinvent the wheel but to use the drag and drop capability of NSApplication and terminate immediately the application after having processed the drag & drop task.

I have another question that’s related to the following point:

My desired application doesn’t have any window, so there is no window object in the xib in which I would check “Visible at launch”. What I want to do is make sure that not even the menu bar appears when I launch. However, there is nothing similar to “Visible at launch” within the menu entry in my xib.

How would I use the methodology you outlined to prevent even the menu bar from appearing in my app initially, and to make the decision later on in my app as to whether to show its menu bar?

OK. Thank you.

If I understand what you want, the following code should be close:

script AppDelegate
	property parent : class "NSObject"
    property headless:0
    property thePath:missing value
	
	on applicationWillFinishLaunching_(aNotification)
		current application's NSMenu's setMenuBarVisible_(0)
        set theData to current application's NSArray's array()
        set desk to POSIX path of (path to desktop) as string
        theData's writeToFile_atomically_(desk & "test.privateType3",1)
	end applicationWillFinishLaunching_
    
    on application_openFile_(theApp,droppedPath)
        set thePath to droppedPath
        set headless to 1
    end
    
    on applicationDidFinishLaunching_(sender)
        if headless is 1 then
            log "we're launching from a drag and drop or opening our custom file type"
            --do whatever you want with the dropped file
            log thePath
            else
            log "we're launching normally"
            current application's NSMenu's setMenuBarVisible_(1)
            current application's NSBundle's loadNibNamed_owner_("Window",me)
        end if
    end

end script

I deleted the window from the default xib file, and then deleted the whole xib file (if I didn’t delete the window first I still got a window even with the xib file deleted – I don’t know how that’s possible). I then added a new xib file called “Window” that is loaded if you start the app normally. I also create an empty array and write that out to a custom file type that will open the app in a headless way if you double click on it. I added 2 document types, one for the custom file type and one that accepts various file types (txt,pdf,xml,app). If you drop any of these types on the app icon it will open the app in a headless manner. The only weird thing, is that when opening headless, it not only turns off the app menu but the whole top menu bar and the dock ( if you click on the desktop anywhere it comes back though).

Ric

Thanks, Ric! This indeed looks like what I want. I’ll experiment with this and report back later with my findings.

Yes, Ric’s suggested methodology indeed worked for me. Thanks again!