A Library for Single-Instance Applications

This page covers a library for single-instance Gnome applications. It is a Google Summer of Code 2006 project and hopefully will be finished on August 21, 2006.

/!\ Attention: guniqueapp is not maintained anymore: please, see GtkApplication and GApplication.

The problem

  • Many appplications, when launched, will contact a previously existing instance and tell it to open a window and then exit so that only one instance of the app is running.
  • Currently there is no unified method for doing this. Every application seems to roll its own method, meaning nearly all of them are buggy with respect to things like startup-notification and focus-stealing-prevention and each and every one has to be fixed individually.
  • Making a library to handle this stuff would allow to remove a lot of code duplication and fix a lot of repeated bugs.

Summer of Code project

This project is expected to improve the above mentioned situation. Details:

  • Name: Vytautas Liuolia

Email: Vytautas.Liuolia at gmail dot com

Project: Making a library for single-instance apps

Project website and source code: http://guniqueapp.akl.lt

Benefits to the Gnome community: A unified library to manage single-instance applications

Deliverables:

  • A library for single-instance applications, implemented using sockets as in BaconMessageConnection.

  • An alternative DBUS backend and an ability to select it automatically, if DBUS is available.
  • Porting some existing applications to use the new infrastructure.

Project schedule: The project is due to be finished on August 21, 2006.

Project mentors: Elijah Newren and Matthias Clasen

Current methods in various applications

Application

Communication method with other instances

Notes

gedit

bacon

Is aware of desktops/workspaces

totem

bacon

gnome-system-monitor

bacon

epiphany

dbus

dbus string: org.gnome.Epiphany

evince

dbus

dbus string: org.gnome.evince.ApplicationService

rhythmbox

bonobo/dbus, optional at compile time

org.gnome.Rhythmbox

liferea

lock file

creates a lock file /.liferea/lock, no communication -- needs fixing

openoffice.org

socket in /tmp (OSL_PIPE*), similar to bacon

problems with startup notification

nautilus

LibUnique

gnome-panel

bonobo

firefox

X methods (see https://bugzilla.mozilla.org/show_bug.cgi?id=223492 for nasty details)

latest versions seem to work fine; Elijah doesn't believe me though

xmms

socket in /tmp

old gtk1 app, probably not targeted in this project

evolution

bonobo

issues with ending startup notification (fixed here)

gnome-terminal

dbus

dbus string: org.gnome.Terminal.Factory

yelp

bonobo (HEAD uses dbus - BrentSmith)

sound-juicer

bacon

User expectations on application startup (visual/desktop issues)

Focus stealing

User expects a newly created window to be activated (focused and raised) by their window manager (Metacity) if, and only if:

  • this window has been created as a consequence of direct user action, for example launching a new application from menu or opening dialog windows within application
  • user has NOT activated another window during the startup process (otherwise Metacity would decline to activate the newly created window)
  • window is expected by the user (think about the annoying "Site www.blablabla.com could not be found" popup warnings from web browsers that often interrupt what you're doing several minutes after you started doing something else)

If windows do not get activated on launch, the window manager should use some mechanism to notify the user that they appeared (and which may need to account for workspaces and viewports). The canonical and current example is to flash them in the taskbar unless they are fully visible.

There are nasty corner cases, like a transient of the focus window that shouldn't get focus, windows that don't appear in the taskbar, windows that don't accept focus, and so on. They're all way out of the scope of this project and are the window manager maintainer's headache (input-only windows, in particular, are totally busted with Metacity right now). More about ICCCM input handling can be found at http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7

Startup notification

  • User expects startup notification to begin, when an action to launch an application is taken (eg a launcher on the panel or a menu item is activated)
  • Startup notification can involve many things. Users will noticed a change of mouse cursor to hourglass symbol or adequate animation and notification of the app being launched in the taskbar. Behind the scenes, there is a transfer of knowledge from the launcher to pagers/taskbars and window managers about when the app was launched, where it should appear (which workspace, screen, etc.), what icon should be used, the name of the executable program being launched, which application launched it, etc.
  • Startup notification (in a case of launching one application) visual representation should end immediately when application displays its work-window (not splash screens or other startup decorations)
  • If there are several applications starting, notification should end when all the applications finished launching
  • The main reason focus-stealing-prevention is broken with single-instance applications is that the launchers set important environment variables that gtk+ normally handles, but that information gets ignored and lost when apps request a previous instance to open a window for them.

The technical details of X protocol for startup notification can be found on http://webcvs.freedesktop.org/startup-notification/startup-notification/doc/startup-notification.txt?view=markup

Workspaces

Users expect newly launched applications to appear in the workspace in which they were launched.

  1. Applications that can do multiple windows for multiple documents, should create a new window in the workspace, if it is not there. GEdit is an example of managing this right, with the exception that no hint has been given to Metacity about what happened so that Metacity doesn't know to activate it.
  2. Applications that cannot provide a second window, or are a one-window applications in nature (like Desktop preferences, for example), should be moved to the workspace where the launch occurred. This is closely related to gnomebug:328080.

Application examples that work incorrectly

  • Evolution (launching a second instance messes up notification)
  • OO.org (ending notification does not work correctly either)

Please note, that while some of the specified expectations are trivial, others may be disputed and only reflect my personal opinion. Feel free to post your attitude in the comments section below.

Implementation details & API

Object Hierarchy

GObject -> GUniqueApp -> GuniqueAppBacon, GUniqueAppDBus

Constructing App object

GUniqueApp* g_unique_app_get (gchar* name); /* Constructs a GUniqueApp object (DBus if present, Bacon otherwise */

GUniqueApp* g_unique_app_get_with_startup_id (gchar *name, gchar* startup_id); /* Same, but with the given startup-id; useful mostly for older GTK versions */

GUniqueAppBacon* g_unique_app_bacon_new (gchar* name);

GUniqueAppBacon* g_unique_app_bacon_new_with_startup_id (gchar* name, gchar* startup_id);

GUniqueAppDBus* g_unique_app_dbus_new (gchar* name);

GUniqueAppDBus* g_unique_app_dbus_new_with_startup_id (gchar* name, gchar* startup_id);

GUniqueApp

gboolean g_unique_app_is_running (GUniqueApp *app); /* Is another instance running? */

void g_unique_app_send_message (GUniqueApp *app, GUniqueAppCommand command, const gchar* data); /* Sends a message to another instance. Please see the convenience functions below, which represent the predefined commands */

void g_unique_app_activate (GUniqueApp *app); /* Activate the running instance */

void g_unique_app_new_document (GUniqueApp *app); /* Create a new document or window */

void g_unique_app_open_uri (GUniqueApp *app, const gchar *uri); /* Open a given file or URI */

void g_unique_app_custom_message (GUniqueApp *app, const gchar *data); /* Send a custom message */

Data types

typedef enum {

  • G_UNIQUE_APP_ACTIVATE, /* Just switch to the already running instance */

  • G_UNIQUE_APP_NEW, /* Create a new document */

  • G_UNIQUE_APP_OPEN, /* Open the given URI */

  • G_UNIQUE_APP_CUSTOM /* Send a custom message */

} GUniqueAppCommand;

GUniqueApp properties

Property

Type

Attributes

"name"

string

Read / Write / Construct Only

"startup-id"

string

Read / Write / Construct Only

"workspace"

guint

Read / Write / Construct Only

GUniqueApp signals

"message" void user_function (GUniqueApp *app, GUniqueAppCommand command, gchar* data, gchar* startup_id, guint workspace, gpointer user_data);

Porting applications to use the new library

I have started porting some applications to the new library, see https://bugzilla.gnome.org/show_bug.cgi?id=351092.

However, some of the applications will be left to port for maintainers, if they are willing to. I have prepared a small howto: GuniqueappUsageGuide.

Comments

Note that there's a recent discussion on the Usability list about this -- all the preference tools should be single instance too -- JoachimNoreiko

"If windows do not steal focus, they should flash in the taskbar and/or use other window manager methods to notify user about the new windows available to deal with." -- this goes against the spec being worked on at Metacity/WindowTypes.

  • It goes against what you said at that page, but note the extra comments I added. I have a couple nitpicks with Vytas' wording (and have modified it above), but mostly I think the idea is correct. However, an important thing to keep in mind is that applications should not be the ones doing focusing -- that's the window manager's job[1]. The focus and startup-notification side of this project is not for deciding focus policy, but for making sure that the window manager has the correct hints from all apps so that it can correctly implement its chosen focus policy.

[1] Yeah, I'm ignoring the globally-active input model of the ICCCM. I think it would be a bad idea for apps to try to use it in most all cases, and it turns out that most apps today don't use it.

Note -- do you guys know about this: http://www.o-hand.com/~iain/single-instance-with-dbus/making-single-instance-programs-with-dbus.html

  • vuntz pointed it out to us on IRC yesterday. Very good resource. :) It is worth noting that anyone who uses it will have broken startup-notification and focus behavior, though.

Comments on the proposed API (MatthiasClasen)

  • g_unique_app_auto_new is a bit of a misnomer. I think the auto is unnecessary, and the new is a bit misleading, since it is not a constructor for a GUniqueApp, it returns one of the subclasses. renamed (VytautasLiuolia)

  • What is the purpose of the name parameter ? Can I create multiple GUniqueApps with different names ? What if I create 2 with the same name ? Name parameter is for appication name, as discussed.
  • g_unique_app_set_window seems to be intended to automate some behaviour in response to commands. What is the idea here, have default behaviour that works 99%, but allow apps to take over ?
  • g_unique_app_set_callback: should probably be a signal on GUniqueApp. Yes, I agree with that (VytautasLiuolia)

Comments on the proposed API (ElijahNewren)

  • I'd really like to remove the timestamps from the API if possible and automatically handle it for the user
  • We may want to consider adding some convenience functions, such as g_unique_app_open_url() (that function alone is probably 95% of what this API will be used for); evince/shell/ev-application.h and http://www.mozilla.org/unix/remote.html may be useful as guides for what might be wanted

Comments on exiting (RossBurton)

  • It came up on usability that applications shouldn't fork() when they start to avoid being attached to a terminal, as that is sometimes useful. i.e setting an editor to $EDITOR allows it to be used when cvs committing, but you can't use gedit for that as if there is another instance running then it quits straight away. An option to block until the task is "complete" would be very useful, then I could for example call gedit --wait foo.txt and it only quits when I close foo.txt in gedit. I hope I'm making sense!

Comments on exiting (DannyMilo)

  • I'd actually say it like that: If an application exits before it is actually done with the task it was called for, then that is a bug. From an un*x point of view this ruins modularity. Also, "an option to block until the task is complete" ("--wait") would be an unbreak-my-application option and should probably just be the (only) default.
  • Do you do all this to get speed improvements or to get size improvements? Depending on which, all design decisions change.
  • Having the application's stdout/stderr go to where the caller puts them seems like common sense, but doesn't work with current single-instance-delegation. Environment variables and arguments as well.
  • Actually it would be nicest if the existing instance's "main()" were reentered. Check out maemo's "maemo-launch" for some pointers: it preloads all the libraries and then just loops and there fork()s and calls "main()". Much faster and more compatible with the unix way.

Comments on DBus (SteveFrécinaux)

  • This patch (adds inheritance support to dbus-glib) may be of interest, at least for the DBus invocation of UniqueApp: it would allow applications inheriting from UniqueApp to define their own additional methods without overriding the ones from the common API.

Comments on Closs Platform Issues (BrentSmith)

  • I know there is pretty much no way that bacon will work in Windows - can/will dbus work and will the API address closs platform issues or is that outside the scope of the project? Unfortunately, I think we are targeting only Unix-like systems -V.L.

    • If it's going to be in gtk, not in gnome, then it could be ported to windows. Named pipes do exist and work on windows. (muntyan)

"Almost single" instance (muntyan).

  • What about not-so-single-instance applications? What I mean is (a real use case), there are two instances running, both listening for input. I run a third one, and this third one needs to tell one of already running instances to open a file or something. It can be done using pid (if you know pid), or it will just pick a random guy. Now the question: can GUniqueApp handle this case? The api looks like it assumes really single instance, it's not clear whether two g_unique_app_send_message() calls will send message to one process or it will choose target randomly each time. An example of such a situation is this: one instance is running, and another one is started with --new-instance option, to quickly edit a file and die. While it may seem to be off-topic here (since "single instance" is about *single* instance), let's remember that the main point of whole GUniqueApp business is to get startup notification and stuff right (the "unique" part is handled by bacon since long ago, and seems to do it well).

Comments on "Almost single" instance (Danny Milo)

  • Yes, if the already-running instance is busy, the new instance shouldn't try talking to it until it is blue in the face, but rather forget about all the SingleInstanceApp business and just do the task himself.

  • For the record: The process boundary is there for good reasons - stability, monitorability, isolation, accountability.

Single instance in SELinux (tedx).

  • I've been dealing with this issue in the SELinux environment where you want single instances of applications for a given context. In particular I'm dealing with MLS (multilevel security) and I need an instance per level (unclassified, confidential, secret ...) so it's a qualified single instance solution. Maybe if there were a hook that could be used to extend the create a new instance decision logic then other unforeseen situations could be handled.

Outreach/SummerOfCode/2006/SingleInstanceApps (last edited 2013-12-03 23:42:11 by WilliamJonMcCann)