how to run NSApplicationMain() in child process?

  • Hi,

    I'm creating a picture viewer controlled from command-line (I call it
    xqiv which sould be an equivalent to http://www.klografx.net/qiv/ on
    linux). However, xqiv is slightly different.

    You have to run xqiv-daemon, which is the window-based application
    which handles global notifications sent by xqiv-command-line and
    displays the image(s). My idea is to have just one application xqiv
    which acts as both daemon and/or command-line tool together. If you
    run xqiv and the deamon is not running yet it forks and run the
    daemon. Parent would act as a commandline tool and child would be the
    daemon which stay on background. Parent would sent a notification to
    the forked child what image to display and exit. If the daemon is
    already running, then fork is not needed and it would just sent the
    notification.

    The question is, how to force  NSApplicationMain() to run in child.

    The basic idea is to do something like this:

    if (already running) {
      return cmd_main(argc, argv);
    } else {
        if (fork() == 0) {
            return NSApplicationMain(argc, (const char **)argv);
        } else {
            return cmd_main(argc, argv);
        }
    }

    The real code is there: http://pastebin.com/7LD2dPK8

    When I run NSApplicationMain() in child process, it simply does
    nothing. What's wrong?

    I have already functional application there:
    https://github.com/smrt28/xqiv

    It is functional but daemon and command-line tool are two applications.

    let me note I'm the beginner in objc/cocoa development and this is my
    very first cocoa application.

    thanks,

    Ondrej
  • I didn't look at your code, but... one thing to note is that it is
    absolutely not secure to run your daemon as root and display a GUI. I hope
    that isn't part of your plan.

    The OS X way to do this sort of thing is to write your daemon as a Launch
    Agent (since it needs to interact with a logged in user). Using a launchd
    plist file, you can tell launchd to monitor a TCP port/unix socket/mach
    port/watched directory/etc and run your agent when there is activity. This
    is nice since your agent can exit when it doesn't have anything to do, and
    the system will regain resources.

    Good place to start would be here:

    http://developer.apple.com/library/mac/#technotes/tn2083/_index.html

    And then here:

    http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPS
    ystemStartup/Chapters/Introduction.html#//apple_ref/doc/uid/10000172i-SW1-S
    W1


    On Wed, May 8, 2013 at 5:41 AM, Ondrej Holecek <ondrej.holecek...>wrote:

    > Hi,
    >
    > I'm creating a picture viewer controlled from command-line (I call it
    > xqiv which sould be an equivalent to http://www.klografx.net/qiv/ on
    > linux). However, xqiv is slightly different.
    >
    > You have to run xqiv-daemon, which is the window-based application
    > which handles global notifications sent by xqiv-command-line and
    > displays the image(s). My idea is to have just one application xqiv
    > which acts as both daemon and/or command-line tool together. If you
    > run xqiv and the deamon is not running yet it forks and run the
    > daemon. Parent would act as a commandline tool and child would be the
    > daemon which stay on background. Parent would sent a notification to
    > the forked child what image to display and exit. If the daemon is
    > already running, then fork is not needed and it would just sent the
    > notification.
    >
    > The question is, how to force  NSApplicationMain() to run in child.
    >
    > The basic idea is to do something like this:
    >
    > if (already running) {
    > return cmd_main(argc, argv);
    > } else {
    > if (fork() == 0) {
    > return NSApplicationMain(argc, (const char **)argv);
    > } else {
    > return cmd_main(argc, argv);
    > }
    > }
    >
    > The real code is there: http://pastebin.com/7LD2dPK8
    >
    > When I run NSApplicationMain() in child process, it simply does
    > nothing. What's wrong?
    >
    > I have already functional application there:
    > https://github.com/smrt28/xqiv
    >
    > It is functional but daemon and command-line tool are two applications.
    >
    > let me note I'm the beginner in objc/cocoa development and this is my
    > very first cocoa application.
    >
    > thanks,
    >
    > Ondrej
    >
  • On Fri, May 10, 2013 at 8:14 AM, Stephen J. Butler
    <stephen.butler...> wrote:
    > I didn't look at your code, but... one thing to note is that it is
    > absolutely not secure to run your daemon as root and display a GUI. I hope
    > that isn't part of your plan.

    No, the "daemon" is the picture viewer itself. It is hidden most of
    the time. When you run "xqiv image1.jpg image2.jpg" from command-line,
    then the "daemon" pops and displays image1.jpg. You can press "escape"
    to hide the application or "space" to see image2.jpg. There is no
    reason to run it as root. The idea is to let the "daeomon" - picture
    windowed application start from command-line tool xqiv if it hasn't
    started yet.

    >
    > The OS X way to do this sort of thing is to write your daemon as a Launch
    > Agent (since it needs to interact with a logged in user). Using a launchd
    > plist file, you can tell launchd to monitor a TCP port/unix socket/mach
    > port/watched directory/etc and run your agent when there is activity. This
    > is nice since your agent can exit when it doesn't have anything to do, and
    > the system will regain resources.
    >
    > Good place to start would be here:
    >
    > http://developer.apple.com/library/mac/#technotes/tn2083/_index.html
    >
    > And then here:
    >
    > http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPS
    ystemStartup/Chapters/Introduction.html#//apple_ref/doc/uid/10000172i-SW1-S
    W1

    >

    thanks for the hint, I have to check this first. However at first
    glance it seems I have to create 2 applications. Daemon and cmd-line
    app. My idea is to have just one app which behaves as a "daemon" in
    case the deamon doesn't run yet and/or as a cmd-line tool if the
    daemon runs. But maybe it is not the way.

    To explain my motivation, I grown on linux bases. I saw Mac and I like
    it, I bought it. However, command-line is something crucial for me. I
    like cmd-line + keyboard much more then Finder + mouse. Am I strange?
    :-)

    > On Wed, May 8, 2013 at 5:41 AM, Ondrej Holecek <ondrej.holecek...>
    > wrote:
    >>
    >> Hi,
    >>
    >> I'm creating a picture viewer controlled from command-line (I call it
    >> xqiv which sould be an equivalent to http://www.klografx.net/qiv/ on
    >> linux). However, xqiv is slightly different.
    >>
    >> You have to run xqiv-daemon, which is the window-based application
    >> which handles global notifications sent by xqiv-command-line and
    >> displays the image(s). My idea is to have just one application xqiv
    >> which acts as both daemon and/or command-line tool together. If you
    >> run xqiv and the deamon is not running yet it forks and run the
    >> daemon. Parent would act as a commandline tool and child would be the
    >> daemon which stay on background. Parent would sent a notification to
    >> the forked child what image to display and exit. If the daemon is
    >> already running, then fork is not needed and it would just sent the
    >> notification.
    >>
    >> The question is, how to force  NSApplicationMain() to run in child.
    >>
    >> The basic idea is to do something like this:
    >>
    >> if (already running) {
    >> return cmd_main(argc, argv);
    >> } else {
    >> if (fork() == 0) {
    >> return NSApplicationMain(argc, (const char **)argv);
    >> } else {
    >> return cmd_main(argc, argv);
    >> }
    >> }
    >>
    >> The real code is there: http://pastebin.com/7LD2dPK8
    >>
    >> When I run NSApplicationMain() in child process, it simply does
    >> nothing. What's wrong?
    >>
    >> I have already functional application there:
    >> https://github.com/smrt28/xqiv
    >>
    >> It is functional but daemon and command-line tool are two applications.
    >>
    >> let me note I'm the beginner in objc/cocoa development and this is my
    >> very first cocoa application.
    >>
    >> thanks,
    >>
    >> Ondrej
    >
    >
  • On Fri, May 10, 2013 at 1:56 AM, Ondrej Holecek <ondrej.holecek...>wrote:

    > thanks for the hint, I have to check this first. However at first
    > glance it seems I have to create 2 applications. Daemon and cmd-line
    > app. My idea is to have just one app which behaves as a "daemon" in
    > case the deamon doesn't run yet and/or as a cmd-line tool if the
    > daemon runs. But maybe it is not the way.
    >

    Make launchd run the same executable, but with a "--daemon" option. That's
    got to be as easy, or easier, than detecting an already running one and
    forking + daemonizing.
  • On Friday, 10. May 2013 at 9:00, Stephen J. Butler wrote:
    > Make launchd run the same executable, but with a "--daemon" option. That's
    > got to be as easy, or easier, than detecting an already running one and
    > forking + daemonizing.

    Detecting is not so hard …

    [NSRunningApplication runningApplicationsWithBundleIdentifier:@"GUI-APP-BUNDLE-ID"]

    … and you can start app in this way …

    [NSWorkspace launchApplicationAtURL:options:configuration:error:]

    … but I wouldn't do this. Stick with launchd.

    Oh, are going to release your app in Mac App Store for example? There're some limitations because of Sandbox.

    BTW if you're command line guy,isn't this ...

    qlmanage -p $PHOTO_PATH1 $PHOTO_PATH2 …

    … enough for you? Different shortcuts, but it does the same thing as your app (didn't look at it, based on your description).
  • On May 8, 2013, at 5:41 AM, Ondrej Holecek wrote:

    > The question is, how to force  NSApplicationMain() to run in child.
    >
    > The basic idea is to do something like this:
    >
    > if (already running) {
    > return cmd_main(argc, argv);
    > } else {
    > if (fork() == 0) {
    > return NSApplicationMain(argc, (const char **)argv);
    > } else {
    > return cmd_main(argc, argv);
    > }
    > }
    >
    > The real code is there: http://pastebin.com/7LD2dPK8
    >
    > When I run NSApplicationMain() in child process, it simply does
    > nothing. What's wrong?

    It is not legal to do very much of anything on the child side of fork() other than exec a new image.  It's no longer online, but I'll quote from Apple's Leopard release notes for Core Foundation:

    "CoreFoundation and fork()

    Due to the behavior of fork(), CoreFoundation cannot be used on the child-side of fork(). If you fork(), you must follow that with an exec*() call of some sort, and you should not use CoreFoundation APIs within the child, before the exec*(). The applies to all higher-level APIs which use CoreFoundation, and since you cannot know what those higher-level APIs are doing, and whether they are using CoreFoundation APIs, you should not use any higher-level APIs either. This includes use of the daemon() function.

    Additionally, per POSIX, only async-cancel-safe functions are safe to use on the child side of fork(), so even use of lower-level libSystem/BSD/UNIX APIs should be kept to a minimum, and ideally to only async-cancel-safe functions.

    This has always been true, and there have been notes made of this on various Cocoa developer mailling lists in the past. But CoreFoundation is taking some stronger measures now to "enforce" this limitation, so we thought it would be worthwhile to add a release note to call this out as well. A message is written to stderr when something uses API which is definitely known not to be safe in CoreFoundation after fork(). If file descriptor 2 has been closed, however, you will get no message or notice, which is too bad. We tried to make processes terminate in a very recognizable way, and did for a while and that was very handy, but backwards binary compatibility prevented us from doing so."

    Regards,
    Ken
  • On Fri, May 10, 2013 at 9:19 AM, Robert Vojta <robert...> wrote:
    > On Friday, 10. May 2013 at 9:00, Stephen J. Butler wrote:
    >
    > Make launchd run the same executable, but with a "--daemon" option. That's
    > got to be as easy, or easier, than detecting an already running one and
    > forking + daemonizing.
    >
    > Detecting is not so hard …
    >
    > [NSRunningApplication
    > runningApplicationsWithBundleIdentifier:@"GUI-APP-BUNDLE-ID"]
    >
    > … and you can start app in this way …
    >
    > [NSWorkspace launchApplicationAtURL:options:configuration:error:]
    >
    > … but I wouldn't do this. Stick with launchd.

    Thanks, I'l try it.

    >
    > Oh, are going to release your app in Mac App Store for example? There're
    > some limitations because of Sandbox.

    I don't think it would fit sandbox. Or do you have any idea how to
    pass command-line application to App Store? :-)

    >
    > BTW if you're command line guy,isn't this ...
    >
    > qlmanage -p $PHOTO_PATH1 $PHOTO_PATH2 …
    >
    > … enough for you? Different shortcuts, but it does the same thing as your
    > app (didn't look at it, based on your description).

    I'm not on my mac, but I guess it's a quick-look app which you can
    reach by clicking an eye button in Finder. I didn't know the
    command-line but executed quick-view by the eye-button from Finder is
    slow. My application loads images on all 4 CPU cores asynchronously
    and cache them up to the settable memory limit. In other words, I
    managed to implement really fast image switching. I haven't seen any
    app switching and showing images as fast. I also make SHA1 sum for
    each image and so I can remember and save whether it was rotated or
    whether I have the same image in the different file.

    I have also a different opinion on window borders, transparency,
    application hiding and exiting etc...
  • On Friday, 10. May 2013 at 12:47, Ondrej Holecek wrote:
    > I don't think it would fit sandbox. Or do you have any idea how to
    > pass command-line application to App Store? :-)
    >
    >

    Just like BBEdit … App in MAS and command line utility downloadable from your website outside of the MAS.
  • On May 9, 2013, at 11:56 PM, Ondrej Holecek <ondrej.holecek...> wrote:

    > No, the "daemon" is the picture viewer itself. It is hidden most of
    > the time.

    That doesn’t sound like a daemon or an agent; those types of things run in the background with no UI. Once there’s a UI, you are writing a regular app. If you don’t want it to “feel” like a full app, with a Dock icon and menu bar, what you’re looking for is the “LSUIElement” key in the app’s Info.plist — this tells the OS that the app just wants to display windows and nothing else. (For example, this is how the Special Characters palette is implemented.)

    > When you run "xqiv image1.jpg image2.jpg" from command-line,
    > then the "daemon" pops and displays image1.jpg.

    All you need is to make an app, maybe with the LSUIElement flag as I said above. Let’s say its bundle ID is com.example.XQIVApp. Then you can implement the ‘xqiv’ command-line tool as a trivial shellscript:

    #!/bin/sh
    open -b com.example.XQIVApp $@

    —Jens
  • On Fri, May 10, 2013 at 2:03 PM, Jens Alfke <jens...> wrote:

    > #!/bin/sh
    > open -b com.example.XQIVApp $@
    >

    You probably want this instead to prevent word splitting for elements that
    have spaces:

    open -b com.example.XQIVApp "$@"

    Nothing is ever trivial in sh/bash ;)
previous month may 2013 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
Go to today