It happened about a year and a half ago: one of our users decided they wanted to be able to use our program with one of the Unix vendors desktops. Not a problem, it should just work, I thought, until I found out they also wanted the application to not start up if a copy was already running (it had clickable desktop icons). A few months later the WWW started to catch on, and thus was born the "helper" application. Once again, I was faced with the problem of having a single instance of our application running at a time, and additionally have arguments passed to the running application for processing.
When I first started investigating a solution, I started with the desktops. Of course, each desktop had its own unique way of launching programs, though some did have ways of limiting programs to a single instance. The next pain was the WWW browsers. The WWW browsers I used had no such mechanism and started up a new helper application with each file. Unwilling to write specific code for each situation, I needed a general solution. I saw several "solutions" in newsgroups utilizing scripts, more, grep, pid's, etc, but they seemed like hacks. Plus these solutions were limited to clients running on the same host, which in X is often not the case. I wanted my solution to work in a general way (launched from a desktop or browser) and be able to pass arguments to an already running instance. It turns out that X provides a mechanism for implementing a solution: properties and selections.
Our first point of view will be the first instance of a client starting up. After connecting to a display it should check the root window to see if the property XA_MY_SELECTION_MANAGER currently has an owner (XGetSelectionOwner()). Where does XA_MY_SELECTION_MANAGER come from? I made it up, and so should you. My application's property is really called XA_WRI_FRONTEND_SELECTION_MANAGER. (In theory you should register your property with the X Registry, but, in practice I assume noone else is going to use XA_WRI_FRONTEND_SELECTION_MANAGER which is a pretty safe bet.) At this point the property should not have an owner (this is the first instance.), and your application should grab ownership of the selection. Note the property is on the root window. If it were not, then whatever window you did create the property on would have to exist throughout your entire session, otherwise the property will disappear when that window is destroyed, not to mention that other clients will have no idea where to look.
You application should then be prepared to ignore all requests concerning this property:
static Boolean refuseToGiveUpSingleManagerCB(Widget _w, Atom *_selection, Atom *_target, Atom *_type, XtPointer *_value, unsigned long *_length, int *_format) { *_value = None; return FALSE; } static void lostSingleManagerCB(Widget _w, Atom *_selection) { XtAppWarning(XtWidgetToApplicationContext(_w), "Someone else has taken control of the selection!\n"); } static bool StartUpSingle(Widget _w) { if (XGetSelectionOwner(XtDisplay(_w), XA_MY_SELECTION_MANAGER) == None) { if (XtOwnSelection(_w, XA_MY_SELECTION_MANAGER, XtLastTimestampProcessed(XtDisplay(_w)), refuseToGiveUpSingleManagerCB, lostSingleManagerCB, NULL)) return TRUE; } return FALSE; }
At this point, a second client that starts up and calls StartUpSingle will fail, and can exit. Pretty simple at this point, but we can do better.
By adding a second property/selection we can have our second client send information to the first. Now when the first client starts up, it should grab ownership of XA_MY_SELECTION_MANAGER and XA_MY_SELECTION. A second client, once StartUpSingle() fails, should now grab ownership of XA_MY_SELECTION from the first client, probably checking that the owner of XA_MY_SELECTION_MANAGER and XA_MY_SELECTION are the same first. Once this is accomplished, the original client should then get the selection value (XtGetSelectionValue()) and regrab ownership. When the second client is asked for the selection value it can put anything it wants in the selection, such as filenames to be opened.
Now we can put
myapp %s
in a script, helper specification, etc., and pass any extra command line arguments in XA_MY_SELECTION. Once passed the second client exits, and the first opens the file.
As a final touch to our program, I added the resource XtNsingleLaunch with valid
values of TRUE, FALSE and QUERY. TRUE enables the single-launch mechanism, FALSE
does not. This allows users to have
myapp -singleLaunch TRUE %s
for a helper specification, or exec line in a desktop script, while still allowing
multiple copies to be run with:
myapp -singleLaunch FALSE
Fially there is QUERY. QUERY directs my application to put up a dialog asking whether the file passed by the second client should be opened.
For a sample implementation of the ideas presented in this column, you may want to investigate http://www.wri.com/~cwikla/xcenter/singleLaunch