From 533d59e2cd30ba79a99a71907ffdda65505e633a Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Wed, 28 Jan 2009 17:19:34 +0000 Subject: Add unique-1.0 requirement (blessed external dependency). Make EShell a subclass of UniqueApp and handle single-instance negotiation. When another Evolution process is running: - Running "evolution" will simply present the existing windows. - Running "evolution -c " will open a shell window set to . - Running "evolution " will open an appropriate window for . The second process will then terminate immediately. svn path=/branches/kill-bonobo/; revision=37147 --- shell/e-shell-module.c | 28 ++--- shell/e-shell-window-actions.c | 5 +- shell/e-shell-window-private.c | 1 - shell/e-shell-window-private.h | 2 +- shell/e-shell-window.c | 9 +- shell/e-shell.c | 237 ++++++++++++++++++++++++++++++++++------- shell/e-shell.h | 12 ++- shell/main.c | 165 ++++++++++++++++++---------- 8 files changed, 335 insertions(+), 124 deletions(-) (limited to 'shell') diff --git a/shell/e-shell-module.c b/shell/e-shell-module.c index 5ec13e00bc..11226557c2 100644 --- a/shell/e-shell-module.c +++ b/shell/e-shell-module.c @@ -44,7 +44,7 @@ struct _EShellModulePrivate { GModule *module; gchar *filename; - EShell *shell; + gpointer shell; /* weak pointer */ gchar *config_dir; gchar *data_dir; @@ -82,7 +82,12 @@ shell_module_set_shell (EShellModule *shell_module, EShell *shell) { g_return_if_fail (shell_module->priv->shell == NULL); - shell_module->priv->shell = g_object_ref (shell); + + shell_module->priv->shell = shell; + + g_object_add_weak_pointer ( + G_OBJECT (shell_module), + &shell_module->priv->shell); } static void @@ -131,22 +136,6 @@ shell_module_get_property (GObject *object, G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } -static void -shell_module_dispose (GObject *object) -{ - EShellModulePrivate *priv; - - priv = E_SHELL_MODULE_GET_PRIVATE (object); - - if (priv->shell != NULL) { - g_object_unref (priv->shell); - priv->shell = NULL; - } - - /* Chain up to parent's dispose() method. */ - G_OBJECT_CLASS (parent_class)->dispose (object); -} - static void shell_module_finalize (GObject *object) { @@ -217,7 +206,6 @@ shell_module_class_init (EShellModuleClass *class) object_class = G_OBJECT_CLASS (class); object_class->set_property = shell_module_set_property; object_class->get_property = shell_module_get_property; - object_class->dispose = shell_module_dispose; object_class->finalize = shell_module_finalize; type_module_class = G_TYPE_MODULE_CLASS (class); @@ -420,7 +408,7 @@ e_shell_module_get_shell (EShellModule *shell_module) { g_return_val_if_fail (E_IS_SHELL_MODULE (shell_module), NULL); - return shell_module->priv->shell; + return E_SHELL (shell_module->priv->shell); } /** diff --git a/shell/e-shell-window-actions.c b/shell/e-shell-window-actions.c index 50749a7698..a9ed7efb09 100644 --- a/shell/e-shell-window-actions.c +++ b/shell/e-shell-window-actions.c @@ -853,9 +853,12 @@ action_new_window_cb (GtkAction *action, EShellWindow *shell_window) { EShell *shell; + const gchar *view_name; shell = e_shell_window_get_shell (shell_window); - e_shell_create_shell_window (shell); + view_name = e_shell_window_get_active_view (shell_window); + + e_shell_create_shell_window (shell, view_name); } /** diff --git a/shell/e-shell-window-private.c b/shell/e-shell-window-private.c index 987c0ab9fb..53f7c28c64 100644 --- a/shell/e-shell-window-private.c +++ b/shell/e-shell-window-private.c @@ -420,7 +420,6 @@ e_shell_window_private_dispose (EShellWindow *shell_window) priv->signal_handler_ids = NULL; } - DISPOSE (priv->shell); DISPOSE (priv->ui_manager); g_hash_table_remove_all (priv->loaded_views); diff --git a/shell/e-shell-window-private.h b/shell/e-shell-window-private.h index 3d0d913863..1f0bec799b 100644 --- a/shell/e-shell-window-private.h +++ b/shell/e-shell-window-private.h @@ -65,7 +65,7 @@ G_BEGIN_DECLS struct _EShellWindowPrivate { - EShell *shell; + gpointer shell; /* weak pointer */ /*** UI Management ***/ diff --git a/shell/e-shell-window.c b/shell/e-shell-window.c index 3272b524e7..82727fecdd 100644 --- a/shell/e-shell-window.c +++ b/shell/e-shell-window.c @@ -153,7 +153,12 @@ shell_window_set_shell (EShellWindow *shell_window, gulong handler_id; g_return_if_fail (shell_window->priv->shell == NULL); - shell_window->priv->shell = g_object_ref (shell); + + shell_window->priv->shell = shell; + + g_object_add_weak_pointer ( + G_OBJECT (shell_window), + &shell_window->priv->shell); /* Need to disconnect these when the window is closing. */ @@ -435,7 +440,7 @@ e_shell_window_get_shell (EShellWindow *shell_window) { g_return_val_if_fail (E_IS_SHELL_WINDOW (shell_window), NULL); - return shell_window->priv->shell; + return E_SHELL (shell_window->priv->shell); } /** diff --git a/shell/e-shell.c b/shell/e-shell.c index 02c9627448..160d637d54 100644 --- a/shell/e-shell.c +++ b/shell/e-shell.c @@ -316,6 +316,37 @@ shell_query_module (EShell *shell, modules_by_scheme, string, shell_module); } +static void +shell_load_modules (EShell *shell) +{ + GDir *dir; + const gchar *dirname; + const gchar *basename; + GError *error = NULL; + + dirname = EVOLUTION_MODULEDIR; + + dir = g_dir_open (dirname, 0, &error); + if (dir == NULL) { + g_critical ("%s", error->message); + g_error_free (error); + return; + } + + while ((basename = g_dir_read_name (dir)) != NULL) { + gchar *filename; + + if (!g_str_has_suffix (basename, "." G_MODULE_SUFFIX)) + continue; + + filename = g_build_filename (dirname, basename, NULL); + shell_query_module (shell, filename); + g_free (filename); + } + + g_dir_close (dir); +} + static gboolean shell_shutdown_timeout (EShell *shell) { @@ -448,6 +479,10 @@ shell_finalize (GObject *object) g_hash_table_destroy (priv->modules_by_name); g_hash_table_destroy (priv->modules_by_scheme); + /* Indicates a clean shut down to the next session. */ + if (!unique_app_is_running (UNIQUE_APP (object))) + e_file_lock_destroy (); + /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -455,42 +490,94 @@ shell_finalize (GObject *object) static void shell_constructed (GObject *object) { - GDir *dir; - EShell *shell; - const gchar *dirname; - const gchar *basename; - GError *error = NULL; + /* UniqueApp will have by this point determined whether we're + * the only Evolution process running. If so, proceed normally. + * Otherwise we just issue commands to the other process. */ + if (unique_app_is_running (UNIQUE_APP (object))) + return; - shell = E_SHELL (object); - dirname = EVOLUTION_MODULEDIR; + e_file_lock_create (); - dir = g_dir_open (dirname, 0, &error); - if (dir == NULL) { - g_critical ("%s", error->message); - g_error_free (error); - return; - } + shell_load_modules (E_SHELL (object)); + e_shell_migrate_attempt (E_SHELL (object)); +} - while ((basename = g_dir_read_name (dir)) != NULL) { - gchar *filename; +static gboolean +shell_message_handle_new (EShell *shell, + UniqueMessageData *data) +{ + gchar *view_name; - if (!g_str_has_suffix (basename, "." G_MODULE_SUFFIX)) - continue; + view_name = unique_message_data_get_text (data); + e_shell_create_shell_window (shell, view_name); + g_free (view_name); - filename = g_build_filename (dirname, basename, NULL); - shell_query_module (shell, filename); - g_free (filename); - } + return TRUE; +} - g_dir_close (dir); +static gboolean +shell_message_handle_open (EShell *shell, + UniqueMessageData *data) +{ + gchar **uris; + + uris = unique_message_data_get_uris (data); + e_shell_handle_uris (shell, uris); + g_strfreev (uris); + + return TRUE; +} + +static gboolean +shell_message_handle_close (EShell *shell, + UniqueMessageData *data) +{ + e_shell_quit (shell); + + return TRUE; +} + +static UniqueResponse +shell_message_received (UniqueApp *app, + gint command, + UniqueMessageData *data, + guint time_) +{ + EShell *shell = E_SHELL (app); + + switch (command) { + case UNIQUE_ACTIVATE: + break; /* use the default behavior */ + + case UNIQUE_NEW: + if (shell_message_handle_new (shell, data)) + return UNIQUE_RESPONSE_OK; + break; + + case UNIQUE_OPEN: + if (shell_message_handle_open (shell, data)) + return UNIQUE_RESPONSE_OK; + break; + + case UNIQUE_CLOSE: + if (shell_message_handle_close (shell, data)) + return UNIQUE_RESPONSE_OK; + break; + + default: + break; + } - e_shell_migrate_attempt (shell); + /* Chain up to parent's message_received() method. */ + return UNIQUE_APP_CLASS (parent_class)-> + message_received (app, command, data, time_); } static void shell_class_init (EShellClass *class) { GObjectClass *object_class; + UniqueAppClass *unique_app_class; parent_class = g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (EShellPrivate)); @@ -502,6 +589,9 @@ shell_class_init (EShellClass *class) object_class->finalize = shell_finalize; object_class->constructed = shell_constructed; + unique_app_class = UNIQUE_APP_CLASS (class); + unique_app_class->message_received = shell_message_received; + /** * EShell:network-available * @@ -748,8 +838,6 @@ shell_init (EShell *shell) e_shell_dbus_initialize (shell); #endif - e_file_lock_create (); - shell_parse_debug_string (shell); g_signal_connect ( @@ -797,7 +885,7 @@ e_shell_get_type (void) }; type = g_type_register_static ( - G_TYPE_OBJECT, "EShell", &type_info, 0); + UNIQUE_TYPE_APP, "EShell", &type_info, 0); } return type; @@ -864,8 +952,6 @@ e_shell_get_shell_windows (EShell *shell) * Returns the canonical name for the #EShellModule whose name or alias * is @name. * - * XXX Not sure this function is worth keeping around. - * * Returns: the canonical #EShellModule name **/ const gchar * @@ -954,6 +1040,7 @@ e_shell_get_shell_settings (EShell *shell) /** * e_shell_create_shell_window: * @shell: an #EShell + * @view_name: name of the initial shell view, or %NULL * * Creates a new #EShellWindow and emits the #EShell::window-created * signal. Use this function instead of e_shell_window_new() so that @@ -962,14 +1049,43 @@ e_shell_get_shell_settings (EShell *shell) * Returns: a new #EShellWindow **/ GtkWidget * -e_shell_create_shell_window (EShell *shell) +e_shell_create_shell_window (EShell *shell, + const gchar *view_name) { GList *active_windows; GtkWidget *shell_window; + UniqueMessageData *data; + UniqueApp *app; g_return_val_if_fail (E_IS_SHELL (shell), NULL); + app = UNIQUE_APP (shell); + + if (unique_app_is_running (app)) + goto unique; + + view_name = e_shell_get_canonical_name (shell, view_name); + + /* EShellWindow initializes its active view from a GConf key, + * so set the key ahead of time to control the intial view. */ + if (view_name != NULL) { + GConfClient *client; + const gchar *key; + GError *error = NULL; + + client = gconf_client_get_default (); + key = "/apps/evolution/shell/view_defaults/component_id"; + gconf_client_set_string (client, key, view_name, &error); + g_object_unref (client); + + if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } + } + shell_window = e_shell_window_new (shell, shell->priv->safe_mode); + unique_app_watch_window (app, GTK_WINDOW (shell_window)); active_windows = shell->priv->active_windows; active_windows = g_list_prepend (active_windows, shell_window); @@ -992,29 +1108,70 @@ e_shell_create_shell_window (EShell *shell) gtk_widget_show (shell_window); return shell_window; + +unique: /* Send a message to the other Evolution process. */ + + /* XXX Do something with UniqueResponse? */ + + if (view_name != NULL) { + data = unique_message_data_new (); + unique_message_data_set_text (data, view_name, -1); + unique_app_send_message (app, UNIQUE_NEW, data); + unique_message_data_free (data); + } else + unique_app_send_message (app, UNIQUE_ACTIVATE, NULL); + + return NULL; } /** - * e_shell_handle_uri: + * e_shell_handle_uris: * @shell: an #EShell - * @uri: the URI to be handled + * @uris: %NULL-terminated list of URIs * - * Emits the #EShell::handle-uri signal. + * Emits the #EShell::handle-uri signal for each URI. * - * Returns: %TRUE if the URI was handled, %FALSE otherwise + * Returns: the number of URIs successfully handled **/ -gboolean -e_shell_handle_uri (EShell *shell, - const gchar *uri) +guint +e_shell_handle_uris (EShell *shell, + const gchar **uris) { - gboolean handled; + UniqueApp *app; + UniqueMessageData *data; + guint n_handled = 0; + gint ii; g_return_val_if_fail (E_IS_SHELL (shell), FALSE); - g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (uris != NULL, FALSE); + + app = UNIQUE_APP (shell); + + if (unique_app_is_running (app)) + goto unique; + + for (ii = 0; uris[ii] != NULL; ii++) { + gboolean handled; + + g_signal_emit ( + shell, signals[HANDLE_URI], + 0, uris[ii], &handled); + n_handled += handled ? 1 : 0; + } + + return n_handled; + +unique: /* Send a message to the other Evolution process. */ + + /* XXX Do something with UniqueResponse? + * XXX set_uris() should take a "const" URI list. */ - g_signal_emit (shell, signals[HANDLE_URI], 0, uri, &handled); + data = unique_message_data_new (); + unique_message_data_set_uris (data, (gchar **) uris); + unique_app_send_message (app, UNIQUE_OPEN, data); + unique_message_data_free (data); - return handled; + return 0; } /** diff --git a/shell/e-shell.h b/shell/e-shell.h index 1503ca776b..9e30550283 100644 --- a/shell/e-shell.h +++ b/shell/e-shell.h @@ -28,6 +28,7 @@ #ifndef E_SHELL_H #define E_SHELL_H +#include #include #include #include @@ -66,12 +67,12 @@ typedef enum _EShellLineStatus EShellLineStatus; * functions below. **/ struct _EShell { - GObject parent; + UniqueApp parent; EShellPrivate *priv; }; struct _EShellClass { - GObjectClass parent_class; + UniqueAppClass parent_class; }; GType e_shell_get_type (void); @@ -85,9 +86,10 @@ EShellModule * e_shell_get_module_by_name (EShell *shell, EShellModule * e_shell_get_module_by_scheme (EShell *shell, const gchar *scheme); EShellSettings *e_shell_get_shell_settings (EShell *shell); -GtkWidget * e_shell_create_shell_window (EShell *shell); -gboolean e_shell_handle_uri (EShell *shell, - const gchar *uri); +GtkWidget * e_shell_create_shell_window (EShell *shell, + const gchar *view_name); +guint e_shell_handle_uris (EShell *shell, + const gchar **uris); void e_shell_send_receive (EShell *shell, GtkWindow *parent); gboolean e_shell_get_network_available (EShell *shell); diff --git a/shell/main.c b/shell/main.c index 0cf8c77d8c..670b24d1a2 100644 --- a/shell/main.c +++ b/shell/main.c @@ -23,6 +23,7 @@ #include #include +#include /* for copied UniqueApp code */ #include #ifdef G_OS_WIN32 @@ -101,8 +102,8 @@ static gboolean disable_eplugin = FALSE; static gboolean disable_preview = FALSE; static gboolean idle_cb (gchar **uris); -static char *requested_view = NULL; -static char *evolution_debug_log = NULL; +static gchar *requested_view = NULL; +static gchar *evolution_debug_log = NULL; static gchar **remaining_args; /* Defined in */ @@ -299,72 +300,38 @@ destroy_config (GConfClient *client) #endif /* DEVELOPMENT */ -static void -open_uris (gchar **uris) -{ - EShell *shell; - guint ii; - - g_return_if_fail (uris != NULL); - - shell = e_shell_get_default (); - - for (ii = 0; uris[ii] != NULL; ii++) - if (!e_shell_handle_uri (shell, uris[ii])) - g_warning ("Invalid URI: %s", uris[ii]); -} - /* This is for doing stuff that requires the GTK+ loop to be running already. */ static gboolean idle_cb (gchar **uris) { EShell *shell; - GtkWidget *shell_window; - const gchar *initial_view; - - g_return_val_if_fail (uris == NULL || g_strv_length (uris) > 0, FALSE); + guint ii; #ifdef KILL_PROCESS_CMD kill_old_dataserver (); #endif - if (uris != NULL) { - open_uris (uris); - return FALSE; - } - shell = e_shell_get_default (); - initial_view = e_shell_get_canonical_name (shell, requested_view); - if (initial_view != NULL) { - GConfClient *client; - const gchar *key; + /* These calls do the right thing when there's another + * Evolution process running. */ + if (uris != NULL && *uris != NULL) + e_shell_handle_uris (shell, uris); + else + e_shell_create_shell_window (shell, requested_view); - client = gconf_client_get_default (); - key = "/apps/evolution/shell/view_defaults/component_id"; - gconf_client_set_string (client, key, initial_view, NULL); - g_object_unref (client); + if (unique_app_is_running (UNIQUE_APP (shell))) { + gtk_main_quit (); + return FALSE; } - shell_window = e_shell_create_shell_window (shell); - -#if 0 /* MBARNES */ - if (shell == NULL) { - /*there is another instance but because we don't open any windows - we must notify the startup was complete manually*/ - gdk_notify_startup_complete (); - bonobo_main_quit (); - } -#endif + /* This must be done after EShell has loaded all the modules. + * For example the mail module makes the global variable `session` + * which is being used by several EPlugins */ - /* This must be done after Bonobo has created all the components. For - * example the mail component makes the global variable `session` which - * is being used by several EPlugins */ - - if (!disable_eplugin) { + if (uris == NULL && !disable_eplugin) e_plugin_load_plugins_with_missing_symbols (); - } return FALSE; } @@ -530,6 +497,84 @@ master_client_die_cb (GnomeClient *client, e_shell_do_quit (shell); } +/* taken from nautilus */ +static guint32 +slowly_and_stupidly_obtain_timestamp (GdkDisplay *display) +{ + Display *xdisplay; + Window xwindow; + XEvent event; + XSetWindowAttributes attrs; + Atom atom_name; + Atom atom_type; + char *name; + + xdisplay = GDK_DISPLAY_XDISPLAY (display); + + attrs.override_redirect = True; + attrs.event_mask = PropertyChangeMask | StructureNotifyMask; + + xwindow = XCreateWindow ( + xdisplay, RootWindow (xdisplay, 0), + -100, -100, 1, 1, + 0, + CopyFromParent, + CopyFromParent, + CopyFromParent, + CWOverrideRedirect | CWEventMask, + &attrs); + + atom_name = XInternAtom (xdisplay, "WM_NAME", TRUE); + g_assert (atom_name != None); + + atom_type = XInternAtom (xdisplay, "STRING", TRUE); + g_assert (atom_type != None); + + name = "Fake Window"; + XChangeProperty ( + xdisplay, xwindow, atom_name, atom_type, + 8, PropModeReplace, (unsigned char *) name, + strlen (name)); + + XWindowEvent ( + xdisplay, xwindow, PropertyChangeMask, &event); + + XDestroyWindow (xdisplay, xwindow); + + return event.xproperty.time; +} + +static gchar * +pick_startup_id (void) +{ + GdkDisplay *display; + const gchar *startup_id; + gchar *id; + + /* XXX This copies logic from unique_app_new(), which we can't use + * because we're subclassing UniqueApp. I already sent ebassi + * a patch to fix this. */ + + display = gdk_display_get_default (); + + /* Try and get the startup notification ID from GDK, the + * environment or, if everything else failed, fake one. */ + startup_id = gdk_x11_display_get_startup_notification_id (display); + + if (!startup_id || startup_id[0] == '\0') + startup_id = g_getenv ("DESKTOP_STARTUP_ID"); + + if (!startup_id || startup_id[0] == '\0') { + guint32 timestamp; + + timestamp = slowly_and_stupidly_obtain_timestamp (display); + id = g_strdup_printf ("_TIME%lu", (gulong) timestamp); + } else + id = g_strdup (startup_id); + + return id; +} + static void create_default_shell (void) { @@ -537,6 +582,7 @@ create_default_shell (void) GConfClient *conf_client; GnomeClient *master_client; gboolean online_mode = TRUE; + gchar *startup_id; GError *error = NULL; conf_client = gconf_client_get_default (); @@ -560,7 +606,16 @@ create_default_shell (void) } } - shell = g_object_new (E_TYPE_SHELL, "online-mode", online_mode, NULL); + startup_id = pick_startup_id (); + + shell = g_object_new ( + E_TYPE_SHELL, + "name", "org.gnome.evolution", + "online-mode", online_mode, + "startup-id", startup_id, + NULL); + + g_free (startup_id); g_signal_connect ( shell, "window-destroyed", @@ -718,6 +773,11 @@ main (int argc, char **argv) gtk_main (); + /* Emit a warning if the shell is not finalized. */ + g_object_unref (default_shell); + if (E_IS_SHELL (default_shell)) + g_warning ("Shell not finalized on exit"); + gtk_accel_map_save (e_get_accels_filename ()); e_icon_factory_shutdown (); @@ -728,8 +788,5 @@ main (int argc, char **argv) link_shutdown (); #endif - /* Indicates a clean shut down to the next session. */ - e_file_lock_destroy (); - return 0; } -- cgit v1.2.3