]> git.0d.be Git - empathy.git/commitdiff
Do not run multiple instances of empathy. Fixes bug #501805 (Marco Barisione).
authorXavier Claessens <xclaesse@src.gnome.org>
Sat, 8 Mar 2008 16:18:53 +0000 (16:18 +0000)
committerXavier Claessens <xclaesse@src.gnome.org>
Sat, 8 Mar 2008 16:18:53 +0000 (16:18 +0000)
svn path=/trunk/; revision=711

src/Makefile.am
src/bacon-message-connection.c [new file with mode: 0644]
src/bacon-message-connection.h [new file with mode: 0644]
src/empathy.c

index 59d6c5c1ffbeac7748e57ac79cf628de6fa31024..31fb417244e97f15253931dc95345ae183e9f50b 100644 (file)
@@ -22,7 +22,10 @@ bin_PROGRAMS =                       \
 libexec_PROGRAMS =             \
        empathy-chat-chandler
 
 libexec_PROGRAMS =             \
        empathy-chat-chandler
 
-empathy_SOURCES = empathy.c
+empathy_SOURCES =                      \
+       empathy.c                       \
+       bacon-message-connection.c      \
+       bacon-message-connection.h
 empathy_accounts_SOURCES = empathy-accounts.c
 empathy_logs_SOURCES = empathy-logs.c
 empathy_chat_chandler_SOURCES = empathy-chat-chandler.c
 empathy_accounts_SOURCES = empathy-accounts.c
 empathy_logs_SOURCES = empathy-logs.c
 empathy_chat_chandler_SOURCES = empathy-chat-chandler.c
diff --git a/src/bacon-message-connection.c b/src/bacon-message-connection.c
new file mode 100644 (file)
index 0000000..c8000de
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2003 Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+
+#include "bacon-message-connection.h"
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+struct BaconMessageConnection {
+       /* A server accepts connections */
+       gboolean is_server;
+
+       /* The socket path itself */
+       char *path;
+
+       /* File descriptor of the socket */
+       int fd;
+       /* Channel to watch */
+       GIOChannel *chan;
+       /* Event id returned by g_io_add_watch() */
+       int conn_id;
+
+       /* Connections accepted by this connection */
+       GSList *accepted_connections;
+
+       /* callback */
+       void (*func) (const char *message, gpointer user_data);
+       gpointer data;
+};
+
+static gboolean
+test_is_socket (const char *path)
+{
+       struct stat s;
+
+       if (stat (path, &s) == -1)
+               return FALSE;
+
+       if (S_ISSOCK (s.st_mode))
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean
+is_owned_by_user_and_socket (const char *path)
+{
+       struct stat s;
+
+       if (stat (path, &s) == -1)
+               return FALSE;
+
+       if (s.st_uid != geteuid ())
+               return FALSE;
+
+       if ((s.st_mode & S_IFSOCK) != S_IFSOCK)
+               return FALSE;
+       
+       return TRUE;
+}
+
+static gboolean server_cb (GIOChannel *source,
+                          GIOCondition condition, gpointer data);
+
+static gboolean
+setup_connection (BaconMessageConnection *conn)
+{
+       g_return_val_if_fail (conn->chan == NULL, FALSE);
+
+       conn->chan = g_io_channel_unix_new (conn->fd);
+       if (!conn->chan) {
+               return FALSE;
+       }
+       g_io_channel_set_line_term (conn->chan, "\n", 1);
+       conn->conn_id = g_io_add_watch (conn->chan, G_IO_IN, server_cb, conn);
+
+       return TRUE;
+}
+
+static void
+accept_new_connection (BaconMessageConnection *server_conn)
+{
+       BaconMessageConnection *conn;
+       int alen;
+
+       g_return_if_fail (server_conn->is_server);
+
+       conn = g_new0 (BaconMessageConnection, 1);
+       conn->is_server = FALSE;
+       conn->func = server_conn->func;
+       conn->data = server_conn->data;
+
+       conn->fd = accept (server_conn->fd, NULL, (guint *)&alen);
+
+       server_conn->accepted_connections =
+               g_slist_prepend (server_conn->accepted_connections, conn);
+
+       setup_connection (conn);
+}
+
+static gboolean
+server_cb (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+       BaconMessageConnection *conn = (BaconMessageConnection *)data;
+       char *message, *subs, buf;
+       int cd, rc, offset;
+       gboolean finished;
+
+       offset = 0;
+       if (conn->is_server && conn->fd == g_io_channel_unix_get_fd (source)) {
+               accept_new_connection (conn);
+               return TRUE;
+       }
+       message = g_malloc (1);
+       cd = conn->fd;
+       rc = read (cd, &buf, 1);
+       while (rc > 0 && buf != '\n')
+       {
+               message = g_realloc (message, rc + offset + 1);
+               message[offset] = buf;
+               offset = offset + rc;
+               rc = read (cd, &buf, 1);
+       }
+       if (rc <= 0) {
+               g_io_channel_shutdown (conn->chan, FALSE, NULL);
+               g_io_channel_unref (conn->chan);
+               conn->chan = NULL;
+               close (conn->fd);
+               conn->fd = -1;
+               g_free (message);
+               conn->conn_id = 0;
+
+               return FALSE;
+       }
+       message[offset] = '\0';
+
+       subs = message;
+       finished = FALSE;
+
+       while (finished == FALSE && *subs != '\0')
+       {
+               if (conn->func != NULL)
+                       (*conn->func) (subs, conn->data);
+
+               subs += strlen (subs) + 1;
+               if (subs - message >= offset)
+                       finished = TRUE;
+       }
+
+       g_free (message);
+
+       return TRUE;
+}
+
+static char *
+find_file_with_pattern (const char *dir, const char *pattern)
+{
+       GDir *filedir;
+       char *found_filename;
+       const char *filename;
+       GPatternSpec *pat;
+
+       filedir = g_dir_open (dir, 0, NULL);
+       if (filedir == NULL)
+               return NULL;
+
+       pat = g_pattern_spec_new (pattern);
+       if (pat == NULL)
+       {
+               g_dir_close (filedir);
+               return NULL;
+       }
+
+       found_filename = NULL;
+
+       while ((filename = g_dir_read_name (filedir)))
+       {
+               if (g_pattern_match_string (pat, filename))
+               {
+                       char *tmp = g_build_filename (dir, filename, NULL);
+                       if (is_owned_by_user_and_socket (tmp))
+                               found_filename = g_strdup (filename);
+                       g_free (tmp);
+               }
+
+               if (found_filename != NULL)
+                       break;
+       }
+
+       g_pattern_spec_free (pat);
+       g_dir_close (filedir);
+
+       return found_filename;
+}
+
+static char *
+socket_filename (const char *prefix)
+{
+       char *pattern, *newfile, *path, *filename;
+       const char *tmpdir;
+
+       pattern = g_strdup_printf ("%s.%s.*", prefix, g_get_user_name ());
+       tmpdir = g_get_tmp_dir ();
+       filename = find_file_with_pattern (tmpdir, pattern);
+       if (filename == NULL)
+       {
+               newfile = g_strdup_printf ("%s.%s.%u", prefix,
+                               g_get_user_name (), g_random_int ());
+               path = g_build_filename (tmpdir, newfile, NULL);
+               g_free (newfile);
+       } else {
+               path = g_build_filename (tmpdir, filename, NULL);
+               g_free (filename);
+       }
+
+       g_free (pattern);
+       return path;
+}
+
+static gboolean
+try_server (BaconMessageConnection *conn)
+{
+       struct sockaddr_un uaddr;
+
+       uaddr.sun_family = AF_UNIX;
+       strncpy (uaddr.sun_path, conn->path,
+                       MIN (strlen(conn->path)+1, UNIX_PATH_MAX));
+       conn->fd = socket (PF_UNIX, SOCK_STREAM, 0);
+       if (bind (conn->fd, (struct sockaddr *) &uaddr, sizeof (uaddr)) == -1)
+       {
+               conn->fd = -1;
+               return FALSE;
+       }
+       listen (conn->fd, 5);
+
+       if (!setup_connection (conn))
+               return FALSE;
+       return TRUE;
+}
+
+static gboolean
+try_client (BaconMessageConnection *conn)
+{
+       struct sockaddr_un uaddr;
+
+       uaddr.sun_family = AF_UNIX;
+       strncpy (uaddr.sun_path, conn->path,
+                       MIN(strlen(conn->path)+1, UNIX_PATH_MAX));
+       conn->fd = socket (PF_UNIX, SOCK_STREAM, 0);
+       if (connect (conn->fd, (struct sockaddr *) &uaddr,
+                               sizeof (uaddr)) == -1)
+       {
+               conn->fd = -1;
+               return FALSE;
+       }
+
+       return setup_connection (conn);
+}
+
+BaconMessageConnection *
+bacon_message_connection_new (const char *prefix)
+{
+       BaconMessageConnection *conn;
+
+       g_return_val_if_fail (prefix != NULL, NULL);
+
+       conn = g_new0 (BaconMessageConnection, 1);
+       conn->path = socket_filename (prefix);
+
+       if (test_is_socket (conn->path) == FALSE)
+       {
+               if (!try_server (conn))
+               {
+                       bacon_message_connection_free (conn);
+                       return NULL;
+               }
+
+               conn->is_server = TRUE;
+               return conn;
+       }
+
+       if (try_client (conn) == FALSE)
+       {
+               unlink (conn->path);
+               try_server (conn);
+               if (conn->fd == -1)
+               {
+                       bacon_message_connection_free (conn);
+                       return NULL;
+               }
+
+               conn->is_server = TRUE;
+               return conn;
+       }
+
+       conn->is_server = FALSE;
+       return conn;
+}
+
+void
+bacon_message_connection_free (BaconMessageConnection *conn)
+{
+       GSList *child_conn;
+
+       g_return_if_fail (conn != NULL);
+       /* Only servers can accept other connections */
+       g_return_if_fail (conn->is_server != FALSE ||
+                         conn->accepted_connections == NULL);
+
+       child_conn = conn->accepted_connections;
+       while (child_conn != NULL) {
+               bacon_message_connection_free (child_conn->data);
+               child_conn = g_slist_next (child_conn);
+       }
+       g_slist_free (conn->accepted_connections);
+
+       if (conn->conn_id) {
+               g_source_remove (conn->conn_id);
+               conn->conn_id = 0;
+       }
+       if (conn->chan) {
+               g_io_channel_shutdown (conn->chan, FALSE, NULL);
+               g_io_channel_unref (conn->chan);
+       }
+
+       if (conn->is_server != FALSE) {
+               unlink (conn->path);
+       }
+       if (conn->fd != -1) {
+               close (conn->fd);
+       }
+
+       g_free (conn->path);
+       g_free (conn);
+}
+
+void
+bacon_message_connection_set_callback (BaconMessageConnection *conn,
+                                      BaconMessageReceivedFunc func,
+                                      gpointer user_data)
+{
+       g_return_if_fail (conn != NULL);
+
+       conn->func = func;
+       conn->data = user_data;
+}
+
+void
+bacon_message_connection_send (BaconMessageConnection *conn,
+                              const char *message)
+{
+       g_return_if_fail (conn != NULL);
+       g_return_if_fail (message != NULL);
+
+       g_io_channel_write_chars (conn->chan, message, strlen (message),
+                                 NULL, NULL);
+       g_io_channel_write_chars (conn->chan, "\n", 1, NULL, NULL);
+       g_io_channel_flush (conn->chan, NULL);
+}
+
+gboolean
+bacon_message_connection_get_is_server (BaconMessageConnection *conn)
+{
+       g_return_val_if_fail (conn != NULL, FALSE);
+
+       return conn->is_server;
+}
+
diff --git a/src/bacon-message-connection.h b/src/bacon-message-connection.h
new file mode 100644 (file)
index 0000000..aac7a2d
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003 Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef BACON_MESSAGE_CONNECTION_H
+#define BACON_MESSAGE_CONNECTION_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef void (*BaconMessageReceivedFunc) (const char *message,
+                                         gpointer user_data);
+
+typedef struct BaconMessageConnection BaconMessageConnection;
+
+BaconMessageConnection *bacon_message_connection_new   (const char *prefix);
+void bacon_message_connection_free                     (BaconMessageConnection *conn);
+void bacon_message_connection_set_callback             (BaconMessageConnection *conn,
+                                                        BaconMessageReceivedFunc func,
+                                                        gpointer user_data);
+void bacon_message_connection_send                     (BaconMessageConnection *conn,
+                                                        const char *message);
+gboolean bacon_message_connection_get_is_server                (BaconMessageConnection *conn);
+
+G_END_DECLS
+
+#endif /* BACON_MESSAGE_CONNECTION_H */
index e16aaf16552e1d36b77aaaf7a6e4ab2dc2343f56..863d307f2bbacfc3d6759d768df7a5f3ed93fc1e 100644 (file)
 #include <config.h>
 
 #include <stdlib.h>
 #include <config.h>
 
 #include <stdlib.h>
+#include <errno.h>
 
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
+#include <gdk/gdkx.h>
 
 #include <libebook/e-book.h>
 #include <libgnomevfs/gnome-vfs.h>
 
 #include <libebook/e-book.h>
 #include <libgnomevfs/gnome-vfs.h>
 #include <libempathy-gtk/empathy-main-window.h>
 #include <libempathy-gtk/empathy-status-icon.h>
 
 #include <libempathy-gtk/empathy-main-window.h>
 #include <libempathy-gtk/empathy-status-icon.h>
 
+#include "bacon-message-connection.h"
+
 #define DEBUG_DOMAIN "EmpathyMain"
 
 #define DEBUG_DOMAIN "EmpathyMain"
 
+static BaconMessageConnection *connection = NULL;
+
 static void
 service_ended_cb (MissionControl *mc,
                  gpointer        user_data)
 static void
 service_ended_cb (MissionControl *mc,
                  gpointer        user_data)
@@ -184,9 +190,87 @@ create_salut_account (void)
        g_object_unref (book);
 }
 
        g_object_unref (book);
 }
 
+/* The code that handles single-instance and startup notification is
+ * copied from gedit.
+ *
+ * Copyright (C) 2005 - Paolo Maggi 
+ */
+static void
+on_bacon_message_received (const char *message,
+                          gpointer    data)
+{
+       GtkWidget *window = data;
+       guint32    startup_timestamp;
+
+       g_return_if_fail (message != NULL);
+
+       empathy_debug (DEBUG_DOMAIN,
+                      "Other instance launched, presenting the main window "
+                      "(message is '%s')", message);
+
+       startup_timestamp = atoi (message);
+
+       /* Set the proper interaction time on the window.
+        * Fall back to roundtripping to the X server when we
+        * don't have the timestamp, e.g. when launched from
+        * terminal. We also need to make sure that the window
+        * has been realized otherwise it will not work. lame. */
+       if (startup_timestamp == 0) {
+               /* Work if launched from the terminal */
+               empathy_debug (DEBUG_DOMAIN, "Using X server timestamp as a fallback");
+
+               if (!GTK_WIDGET_REALIZED (window)) {
+                       gtk_widget_realize (GTK_WIDGET (window));
+               }
+
+               startup_timestamp = gdk_x11_get_server_time (window->window);
+       }
+
+       gtk_window_present_with_time (GTK_WINDOW (window), startup_timestamp);
+}
+
+static guint32
+get_startup_timestamp ()
+{
+       const gchar *startup_id_env;
+       gchar       *startup_id = NULL;
+       gchar       *time_str;
+       gchar       *end;
+       gulong       retval = 0;
+
+       /* we don't unset the env, since startup-notification
+        * may still need it */
+       startup_id_env = g_getenv ("DESKTOP_STARTUP_ID");
+       if (startup_id_env == NULL) {
+               goto out;
+       }
+
+       startup_id = g_strdup (startup_id_env);
+
+       time_str = g_strrstr (startup_id, "_TIME");
+       if (time_str == NULL) {
+               goto out;
+       }
+
+       errno = 0;
+
+       /* Skip past the "_TIME" part */
+       time_str += 5;
+
+       retval = strtoul (time_str, &end, 0);
+       if (end == time_str || errno != 0)
+               retval = 0;
+
+ out:
+       g_free (startup_id);
+
+       return (retval > 0) ? retval : 0;
+}
+
 int
 main (int argc, char *argv[])
 {
 int
 main (int argc, char *argv[])
 {
+       guint32            startup_timestamp;
        EmpathyStatusIcon *icon;
        GtkWidget         *window;
        MissionControl    *mc;
        EmpathyStatusIcon *icon;
        GtkWidget         *window;
        MissionControl    *mc;
@@ -201,6 +285,8 @@ main (int argc, char *argv[])
        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
        textdomain (GETTEXT_PACKAGE);
 
        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
        textdomain (GETTEXT_PACKAGE);
 
+       startup_timestamp = get_startup_timestamp ();
+
        if (!gtk_init_with_args (&argc, &argv,
                                 _("- Empathy Instant Messenger"),
                                 NULL, GETTEXT_PACKAGE, &error)) {
        if (!gtk_init_with_args (&argc, &argv,
                                 _("- Empathy Instant Messenger"),
                                 NULL, GETTEXT_PACKAGE, &error)) {
@@ -215,6 +301,31 @@ main (int argc, char *argv[])
                                           PKGDATADIR G_DIR_SEPARATOR_S "icons");
        gnome_vfs_init ();
 
                                           PKGDATADIR G_DIR_SEPARATOR_S "icons");
        gnome_vfs_init ();
 
+        /* Setting up the bacon connection */
+       connection = bacon_message_connection_new ("empathy");
+       if (connection != NULL) {
+               if (!bacon_message_connection_get_is_server (connection)) {
+                       gchar *message;
+
+                       empathy_debug (DEBUG_DOMAIN, "Activating existing instance");
+
+                       message = g_strdup_printf ("%" G_GUINT32_FORMAT,
+                                                  startup_timestamp);
+                       bacon_message_connection_send (connection, message);
+
+                       /* We never popup a window, so tell startup-notification
+                        * that we are done. */
+                       gdk_notify_startup_complete ();
+
+                       g_free (message);
+                       bacon_message_connection_free (connection);
+
+                       return EXIT_SUCCESS;
+               }
+       } else {
+               g_warning ("Cannot create the 'empathy' bacon connection.");
+       }
+
        /* Setting up MC */
        monitor = mc_account_monitor_new ();
        mc = empathy_mission_control_new ();
        /* Setting up MC */
        monitor = mc_account_monitor_new ();
        mc = empathy_mission_control_new ();
@@ -243,6 +354,13 @@ main (int argc, char *argv[])
        window = empathy_main_window_show ();
        icon = empathy_status_icon_new (GTK_WINDOW (window));
 
        window = empathy_main_window_show ();
        icon = empathy_status_icon_new (GTK_WINDOW (window));
 
+       if (connection) {
+               /* We se the callback here because we need window */
+               bacon_message_connection_set_callback (connection,
+                                                      on_bacon_message_received,
+                                                      window);
+       }
+
        gtk_main ();
 
        empathy_idle_set_state (idle, MC_PRESENCE_OFFLINE);
        gtk_main ();
 
        empathy_idle_set_state (idle, MC_PRESENCE_OFFLINE);