Different ways of launching a Cocoa app from the command line

Hi,


I wonder what are the differences between starting a Cocoa app from the command line via "/usr/bin/open" vs. directly executing the binary inside the app bundle in Contents/MacOS.


One difference that I noticed is that by starting the binary directly, the "Environment variables" from the Info.plist file are ignored (i.e. are not available in the application via ProcessInfo.processInfo.environment).


Unfortunately I was unable to find much documentation on this topic. May be somebody can shed some light on this?


Thank you,

Andreas

Replies

There are lots of differences, ranging from UNIXy stuff (

stdin
,
stdout
,
stderr
,
environ
, current working directory, resource limits, and so on) to Mac-specific stuff (Mach bootstrap namespace, security context, and so on). There’s a somewhat outdated discussion of this in TN2083 but a) it’s certainly not comprehensive, and b) that’s not the main focus of that doc.

My question is, why do you care? Users don’t run GUI apps from Terminal (or at least they shouldn’t), so you shouldn’t provide official support for that. And any mechanism you have in place for launching a GUI app should be launching it via high-level facilities (like NSWorkspace, which is what

open
uses) rather than low-level POSIX APIs.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn,


The reason we ask is because we are experiencing different behaviors when we launch an applicaiton as a Launch Agent via launchd.


If an application is invoked via /usr/bin/open, for instance with the plist below, it will open when lthe aunchctl load happens, and there are two processes around, the /usr/bin/open process and the calculator process. However, when launchctl unload happens, the /usr/bin/open process disappears but the process spawned by open remains. The application keeps running.


It can be easily reproduced for instance with the Calculator application:


Lauch Agent registration plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.classycode.calculator</string>
  <key>ProgramArguments</key>
  <array>
  <string>/usr/bin/open</string>
  <string>-W</string>
  <string>/Applications/Calculator.app</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
</dict>
</plist>


Process output after launchctl load

$ ps aux | grep -i calculator
ahs              5033   0.0  0.3  2659796  58500   ??  S     9:57AM   0:00.38 /Applications/Calculator.app/Contents/MacOS/Calculator
ahs              5027   0.0  0.1  2547604  20484   ??  S     9:57AM   0:00.04 /usr/bin/open -W /Applications/Calculator.app


Process output after launchctl unload

$ ps aux | grep -i calculator
ahs              5033   0.0  0.3  2658696  58460   ??  S     9:57AM   0:00.59 /Applications/Calculator.app/Contents/MacOS/Calculator


Regards

Alex

However, when launchctl unload happens, the /usr/bin/open process disappears but the process spawned by open remains.

Right. This is because, if a job is started when you unload it,

launchd
terminates it before the unload. This termination process uses UNIX signals, and it targets the process group assigned to the job. If you fork/exec from a launchd job, the child process ends up in the same process group as the parent (see
killpg
), and thus the child process gets terminated. OTOH, the
open
tool runs the app via NSWorkspace, which causes it to act just like it was launched by the user from Finder, which means it’s in a different process group, and thus doesn’t get terminated.

Note Some of this is discussed in

launchd.plist
.

It’s generally not a good idea to run a GUI app directly as a launchd agent. You would typically have the agent start the app and then have some other mechanism to terminate it. The best way to do that depends on your specific circumstances. Can you explain more about the big picture here?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

"Hi Quinn,


Thank you for your answer, and pardon my late reply. That clarifies things a little.


I'm a bit confused about your comment about running a GUI app as launch agent being a bad idea. We are writing a GUI application for one of our clients that is supposed to run as soon as the user logs in, and should be always running. We can achieve this behavior by registering it as a launch agent. The application is mostly invisible (we display a menu bar icon to access its settings), and only pops up a window when a print job is processed by the CUPS printing system. The user can then associate some info with the job that was printed and the window disappears again. It's basically a local print acounting solution. We also take advantage of KeepAlive to automatically relaunch the application should a crash occur.


Are there any guides, patterns or best practices on achieving this kind of behavior? Or to ask more bluntly, what is wrong with the way we do things now? Unfortunately the "Designing Daemons and Services" guide does not provide much insight here, only mentioning that "They (agents) can display a visual interface, but this is not recommended.".

I’ve just noticed that there are two folks on this thread; are you and aschweiz77 working on the same project?

The application is mostly invisible (we display a menu bar icon to access its settings), and only pops up a window when a print job is processed by the CUPS printing system.

We’re suffering from terminological confusion here. When aschweiz77 started this thread they wrote Cocoa app and then you followed up with Calculator as an example, leading me to believe that you were working on a standard GUI app, one with an icon in the Dock and a menu bar. This does not work well as a launchd agent because the user expects to be able to quit the app, and they’ll be kinda peeved when launchd merrily relaunches it for them.

What you’re creating is commonly known as a UI element; these generally work out OK as a launchd agent.

However, I wonder if you could do better. Right now you have a complete GUI app permanently running in the background, even if the user never prints for the entire login session. Wouldn’t it be better to monitor the printing system with a lightweight launchd agent and then, when there is UI work to be done, launch your UI element to do that work?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn,


Yeah, we are working on the same project. 🙂


Good to hear that our approach is not completely wrong for what we are building. We could go down the route you describe, but the app also offers some functionality through the menu bar icon, like a list of past print jobs, and browser links to the client's web platform, which the client wants to be always present.


Kind Regards

Alex