empathy r711 - trunk/src



Author: xclaesse
Date: Sat Mar  8 16:18:53 2008
New Revision: 711
URL: http://svn.gnome.org/viewvc/empathy?rev=711&view=rev

Log:
Do not run multiple instances of empathy. Fixes bug #501805 (Marco Barisione).


Added:
   trunk/src/bacon-message-connection.c
   trunk/src/bacon-message-connection.h
Modified:
   trunk/src/Makefile.am
   trunk/src/empathy.c

Modified: trunk/src/Makefile.am
==============================================================================
--- trunk/src/Makefile.am	(original)
+++ trunk/src/Makefile.am	Sat Mar  8 16:18:53 2008
@@ -22,7 +22,10 @@
 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

Added: trunk/src/bacon-message-connection.c
==============================================================================
--- (empty file)
+++ trunk/src/bacon-message-connection.c	Sat Mar  8 16:18:53 2008
@@ -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;
+}
+

Added: trunk/src/bacon-message-connection.h
==============================================================================
--- (empty file)
+++ trunk/src/bacon-message-connection.h	Sat Mar  8 16:18:53 2008
@@ -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 */

Modified: trunk/src/empathy.c
==============================================================================
--- trunk/src/empathy.c	(original)
+++ trunk/src/empathy.c	Sat Mar  8 16:18:53 2008
@@ -23,10 +23,12 @@
 #include <config.h>
 
 #include <stdlib.h>
+#include <errno.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>
@@ -45,8 +47,12 @@
 #include <libempathy-gtk/empathy-main-window.h>
 #include <libempathy-gtk/empathy-status-icon.h>
 
+#include "bacon-message-connection.h"
+
 #define DEBUG_DOMAIN "EmpathyMain"
 
+static BaconMessageConnection *connection = NULL;
+
 static void
 service_ended_cb (MissionControl *mc,
 		  gpointer        user_data)
@@ -184,9 +190,87 @@
 	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[])
 {
+	guint32            startup_timestamp;
 	EmpathyStatusIcon *icon;
 	GtkWidget         *window;
 	MissionControl    *mc;
@@ -201,6 +285,8 @@
 	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)) {
@@ -215,6 +301,31 @@
 					   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 ();
@@ -243,6 +354,13 @@
 	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);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]