After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 378260 - add support for single instance applications
add support for single instance applications
Status: RESOLVED FIXED
Product: gtk+
Classification: Platform
Component: Widget: Other
unspecified
Other Linux
: Normal enhancement
: ---
Assigned To: gtk-bugs
gtk-bugs
: 170782 (view as bug list)
Depends on:
Blocks: 162822
 
 
Reported: 2006-11-22 21:36 UTC by Emmanuele Bassi (:ebassi)
Modified: 2010-07-10 03:15 UTC
See Also:
GNOME target: ---
GNOME version: ---



Description Emmanuele Bassi (:ebassi) 2006-11-22 21:36:38 UTC
SSIA

as a starting point, there's GtkUnique API:

  http://devel.emmanuelebassi.net/API/gtkunique/index.html

GtkUnique is available inside the gtkunique module of GNOME's CVS.

the basic layout is composed of a base class (GtkUniqueAppObject) which is implemented by different backends (chosen at compile time); the backend implementation is hidden by a GtkUniqueApp class.

for inclusion in GTK+, I'd move the backends on the target library of GDK, thus adding a GdkUniqueObject class; the class available to GTK+ would be GtkUniqueApp.

the IPC backends available are: D-Bus, Xlibs and unix domain sockets (similar to libbacon); if GTK+ doesn't want to add a dependency on D-Bus the Xlibs backend is perfectly fine on the x11 GDK target.  on the quartz and directfb backends, the unix domain socket is the logical choice;  as for win32, the unix domain sockets can be emulated by named pipe objects, as far as I know.
Comment 1 Havoc Pennington 2006-11-23 06:03:35 UTC
A quick thing I noticed about the API, it looks like you are supposed to do something like:

 if (unique app is running)
    exit(0);
 else
    run();

This has to be a race condition though, as far as I can see. Both instances could end up exiting, or both could end up running. The way X selections and D-Bus bus names work, you are supposed to do this:

 send_request_to_be_unique_app();
 if (I am now the app)
   run();
 else
   exit(0);

Both X selections and D-Bus bus names also offer notification if you are no longer the single instance, i.e. you can implement "--replace" (as in "metacity --replace" which uses an X selection). In that case a special flag passed to send_request_to_be_unique_app causes the replacing instance to take over and the old instance to get a "you have lost the name" signal.

It's possible you could design an API so one codepath handles both "you never got the name in the first place" and "you have lost the name" - maybe you could require apps to connect to a signal that indicates whether they are the current instance, and so the API would be like:

 connect(unique, "ownership-changed", ownership_changed_cb);
 send_request_to_be_unique_app();

 void ownership_changed_cb(OwnershipState state) {
    if (state == NOT_OWNER) {
      exit(0);
    } else if (state == OWNER) {
      open_untitled_document();
    }
 }

This is definitely a little complicated to use, though, and it's unclear 
--replace makes sense for a document-oriented app; it might only make
sense for things like gnome-settings-daemon or the panel or whatever.
Comment 2 Havoc Pennington 2006-11-23 06:29:22 UTC
Some other thoughts...

1) I wonder if a way to think about this API is that the "client" side of it should look just like launching a .desktop file in terms of the kinds of args you can pass in and the relevant setup such as starting a startup notification sequence.

In other words, for each %u/%f/etc. type thing allowed on an Exec line, and for each environment variable, etc. that might be used when launching a .desktop file, there would be a corresponding way to forward that info to the single instance.

This naturally leads to a new key-value pair in the desktop file which allows launching apps directly with a unique app message and not a fork/exec, which could mean a very fast way to open new windows - no new X or dbus connection would need to be created by a new process.

2) There's a fair bit of how to structure the app that could maybe be done automatically; you need to have your "daemon"/server mode for the app and your "hand off to the daemon"/client part of the app.

The app doesn't need to care whether it got the startup id, workspace, etc. from the command line directly or from a unique app message, though.

The idea here is to change the "message" signal to be pretty much the main() for the application, so you write an app something like this:

void
run_my_app_callback(const char *startup_id, int workspace, ...)
{
   /* open document window or whatever */

}

int main(int argc, char **argv)
{
  GtkUniqueApp *app;

  app = gtk_unique_app_new("org.gnome.MyApp");
  g_signal_connect(app, "run", run_my_app_callback);

  gtk_unique_app_init(&argc, &argv);
  gtk_main();
  return 0;
}

Here, gtk_init() has been replaced. gtk_unique_app_init() just fires off the request to become the instance; then inside gtk_main(), if the response is that we are the instance the run_my_app_callback() gets run, otherwise some other callback is run or by default maybe gtk just exits. If the app is launched a second time then run_my_app_callback() is invoked again.

"run" is a bad name for the signal and run_my_app_callback() is a bad name for the callback, would need to think on that.

3) Tons of args to the "message" signal is a little clunky, but more important it's not extensible at all... there are various possible solutions, one might be to have a "new window request" object with properties such as "startup id" and "screen" and what have you.

A handy convenience function might be
 GtkWindow* gtk_new_window_request_create_window(request)
or
 gtk_new_window_request_setup_window(window)

where this would set up the screen, workspace, startup id, etc. for that window according to the incoming request.
Comment 3 Benoît Dejean 2006-12-20 15:08:22 UTC
how is this related to http://bugzilla.gnome.org/show_bug.cgi?id=351092 ?
Comment 4 Emmanuele Bassi (:ebassi) 2006-12-20 16:02:10 UTC
guniqueapp is not actively maintained; it's missing an X11 backend; the backends are not cleanly separated from the rest of the code; the unix domain socket backend is just a dump of libbacon; it doesn't use a bidirectional IPC channel for error/user interaction usage; its code base does not conform to GTK+ code style.

finally, gtkunique and guniqueapp API are basically the same, if we are going to import the functionality following havoc's suggestions.

so, in short, gtkunique can be considered a more polished and more functional evolution of guniqueapp.
Comment 5 Emmanuele Bassi (:ebassi) 2006-12-21 22:22:00 UTC
I've finally some time to reply.

(In reply to comment #1)

> A quick thing I noticed about the API, it looks like you are supposed to do
> something like:
> 
>  if (unique app is running)
>     exit(0);
>  else
>     run();
> 
> This has to be a race condition though, as far as I can see.

yes, and no.  it depends on how the used back-end works, mostly.  both the D-Bus and unix domain sockets back-ends create a "name" as soon as is_running() fails (this is not done in CVS, as we speak, but it was how gtkunique started and it should be reverted as soon as I find a bit of time to do it).

the race condition, instead, exists inside the Xlibs backend because of how we use the X window properties to send commands and receive a response.  I thought how to fix this and it should work - but I still need to do some preliminary tests.

in short:

  if (unique app is running)
    response = send (command)
    exit (response == 'ok)
  else
    window = create app window
    add window to unique app
    run ()

is safe because "unique app is running" should reserve the name used to create a new unique application instance object, thus combining the "send request" and the "am I the first instance?" phases.

(In reply to comment #2)
> Some other thoughts...
> 
> 1) I wonder if a way to think about this API is that the "client" side of it
> should look just like launching a .desktop file in terms of the kinds of args
> you can pass in and the relevant setup such as starting a startup notification
> sequence.

yeah.  other environments honour a key like:

  SingleInstance=yes

which does this for every application launched through a dot-desktop file.

as for point 2) and 3) you're right, and a gtkunique-in-gtk approach should definitely hide some of the low-level machinery gtkunique has in place.
Comment 6 Bastien Nocera 2007-04-16 19:33:55 UTC
(In reply to comment #4)
> guniqueapp is not actively maintained; it's missing an X11 backend; the
> backends are not cleanly separated from the rest of the code; the unix domain
> socket backend is just a dump of libbacon; it doesn't use a bidirectional IPC
> channel for error/user interaction usage; its code base does not conform to

I don't see the bidirectional IPC in the GtkUnique API.

Totem has something like:
                line = totem_option_create_line (TOTEM_REMOTE_COMMAND_SHOW_PLAYING);
                bacon_message_connection_set_callback (conn,
                                totem_print_playing_cb, loop);
                bacon_message_connection_send (conn, line);

And totem_print_playing_cb will get the answer, and print out what's playing.
Comment 7 Emmanuele Bassi (:ebassi) 2007-04-16 20:48:21 UTC
(In reply to comment #6)

> I don't see the bidirectional IPC in the GtkUnique API.

the bidirectionality is implemented by the GtkUniqueResponse code that the callbacks for the ::message signal must return. the currently running instance can return a code depending on the user action (GTK_UNIQUE_RESPONSE_CANCEL), application error (GTK_UNIQUE_RESPONSE_ABORT) or library/IPC error (GTK_UNIQUE_RESPONSE_FAIL). this code should be handled by the sender or the message in order to return the appropriate error code to the console or whatever action is deemed necessary (for instance, launching a second instance of the application with another user profile, like mozilla does).
Comment 8 Bastien Nocera 2007-04-16 21:00:31 UTC
(In reply to comment #7)
> (In reply to comment #6)
> 
> > I don't see the bidirectional IPC in the GtkUnique API.
> 
> the bidirectionality is implemented by the GtkUniqueResponse code that the
> callbacks for the ::message signal must return. the currently running instance
> can return a code depending on the user action (GTK_UNIQUE_RESPONSE_CANCEL),
> application error (GTK_UNIQUE_RESPONSE_ABORT) or library/IPC error
> (GTK_UNIQUE_RESPONSE_FAIL). this code should be handled by the sender or the
> message in order to return the appropriate error code to the console or
> whatever action is deemed necessary (for instance, launching a second instance
> of the application with another user profile, like mozilla does).

But there's no way to push data back to the sender of the original message apart from success of failure...

Comment 9 Havoc Pennington 2007-04-16 21:10:34 UTC
Why would a unique app convenience API support sending random commands and requests (vs. just doing this with an IPC API like dbus or I guess libbacon)?

I have to admit some confusion on why there should be multiple backends instead of just picking dbus/libbacon/x11/something (I can see why we need one unix/x11 and one win32 backend, but on unix why not just pick something).

If we need a cross-platform windows/unix general IPC library, that seems like something that should be designed separately, not crammed into the unique app api.
Comment 10 Elijah Newren 2007-05-12 20:20:26 UTC
Historical note: Vytautus put support for multiple backends into guniqueapp simply because it was not clear at the time what should be used (note that D-Bus was not stable back then).  In the end, we were more interested in making sure working functionality existed and that metacity and gtk+ were patched to include the necessary support, so we just punted and decided to write backends to make sure that whatever was picked we'd already have figured out.  I suspect GtkUnique merely continued that support for multiple backends since it was already there.
Comment 11 Havoc Pennington 2007-08-21 23:11:30 UTC
*** Bug 170782 has been marked as a duplicate of this bug. ***
Comment 12 Havoc Pennington 2007-08-21 23:21:11 UTC
A post with an API idea:

http://mail.gnome.org/archives/gtk-devel-list/2007-August/msg00039.html
http://mail.gnome.org/archives/gtk-devel-list/2007-August/msg00040.html

The main point is to put most of the main() for gtkunique into the library, see 
http://svn.gnome.org/viewcvs/gtkunique/trunk/tests/test-unique.c?revision=6&view=markup for how gtkunique main() looks now. The library can do the getting of startup id, parsing the command line options, deciding whether to do gtk_main() or just forward to the other app, etc.

Some other smaller points:
- dbus bus name explicitly provided by the app developer
- API encourages handling "name lost" and could auto-implement --replace
- using an extensible "dict of properties" for the parameters to an activation, avoiding long list of args to the "message" signal



Comment 13 Havoc Pennington 2007-10-06 20:18:59 UTC
Action plan for how to add dbus-dependent stuff to GTK:
http://mail.gnome.org/archives/gtk-devel-list/2007-September/msg00139.html
Comment 14 Jonh Wendell 2008-07-30 17:05:07 UTC
With gtk 3 coming, it would be a great thing to have merged.

Perhaps a GtkApplication class where 'SingleInstance' would be a flag...

On the other side, with an Application class (which owns several windows), it would be easier to window manager to know the windows whom belong to each application. I guess.
Comment 15 Matthias Clasen 2010-07-10 03:15:53 UTC
I think GtkApplicaiton covers this