Commit 66e5636d authored by Carlos Garcia Campos's avatar Carlos Garcia Campos

Run evince instances in different processes instead of single instance

 - When built with DBus support a daemon is used to keep track of opened
   documents and reload them when reopened.

 - Crash recovery code has been removed.

 - Metadata migration code has been moved from main.c to ev-daemon.c, so
   that it's only run once on daemon startup.

Fixes bgo#583680, bgo#434966, bgo#497388, bgo#524633 and bgo#586087.
parent 3f28adc1
......@@ -269,6 +269,10 @@ AC_SUBST([DBUS_LIBS])
AM_CONDITIONAL([ENABLE_DBUS], [test "$enable_dbus" = "yes"])
if test "$enable_dbus" = "yes"; then
PKG_CHECK_MODULES([EV_DAEMON], [gthread-2.0 gio-2.0 >= $GLIB_REQUIRED dbus-glib-1 >= $DBUS_GLIB_REQUIRED])
fi
dnl ========= Check for GConf
AC_MSG_CHECKING([whether GConf support is requested])
......@@ -339,6 +343,10 @@ FRONTEND_LIBS="$FRONTEND_CORE_LIBS -lz"
AC_SUBST(FRONTEND_CFLAGS)
AC_SUBST(FRONTEND_LIBS)
EV_DAEMON_CFLAGS="$EV_DAEMON_CFLAGS $DEBUG_FLAGS"
AC_SUBST([EV_DAEMON_CFLAGS])
AC_SUBST([EV_DAEMON_LIBS])
# Check for Nautilus property page build
AC_ARG_ENABLE([nautilus],
[AS_HELP_STRING([--disable-nautilus],[Build the nautilus extensions])],
......
......@@ -36,6 +36,17 @@ DESKTOP_FILES= $(DESKTOP_IN_FILES:.desktop.in.in=.desktop)
desktopdir = $(datadir)/applications
desktop_DATA = $(DESKTOP_FILES)
#
# DBus servide file
#
if ENABLE_DBUS
servicedir = $(datadir)/dbus-1/services
service_in_files = org.gnome.evince.Daemon.service.in
service_DATA = $(service_in_files:.service.in=.service)
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@
endif
#
# GConf schema
......@@ -102,12 +113,13 @@ update-icon-cache:
# Extra files to be included in the tarball
#
EXTRA_DIST = \
$(ui_DATA) \
$(DESKTOP_IN_FILES) \
$(schema_in_files) \
$(man_MANS) \
$(NULL)
EXTRA_DIST = \
$(ui_DATA) \
$(DESKTOP_IN_FILES) \
$(schema_in_files) \
org.gnome.evince.Daemon.service.in \
$(man_MANS) \
$(NULL)
#
# Clean up properly
......@@ -115,4 +127,5 @@ EXTRA_DIST = \
DISTCLEANFILES = \
$(DESKTOP_FILES) \
$(schema_DATA)
$(schema_DATA) \
$(service_DATA)
[D-BUS Service]
Name=org.gnome.evince.Daemon
Exec=@libexecdir@/evinced
......@@ -26,6 +26,9 @@ INCLUDES= \
bin_PROGRAMS=evince
libexec_PROGRAMS=evince-convert-metadata
if ENABLE_DBUS
libexec_PROGRAMS += evinced
endif
EV_MEDIA_PLAYER_KEYS_SOURCES = ev-media-player-keys.c ev-media-player-keys.h
if ENABLE_DBUS
......@@ -119,10 +122,30 @@ evince_convert_metadata_SOURCES= \
evince_convert_metadata_LDADD= \
$(SHELL_LIBS)
if ENABLE_DBUS
BUILT_SOURCES += ev-daemon-service.h
evinced_SOURCES= \
ev-daemon.c
evinced_CFLAGS= \
-DDATADIR=\"$(pkgdatadir)\" \
-DGNOMEDATADIR=\"$(datadir)\" \
-I$(top_srcdir) \
-I$(top_builddir) \
-DLIBEXECDIR=\""$(libexecdir)"\" \
-DEVINCE_COMPILATION \
$(EV_DAEMON_CFLAGS) \
$(WARN_CFLAGS) \
$(DISABLE_DEPRECATED)
evinced_LDADD= \
$(EV_DAEMON_LIBS)
endif
EXTRA_DIST = ev-marshal.list \
ev-application-service.xml \
ev-daemon-service.xml \
$(EV_MEDIA_PLAYER_KEYS_SOURCES)
......@@ -133,8 +156,12 @@ ev-marshal.c: $(srcdir)/ev-marshal.list
echo '#include "ev-marshal.h"' > ev-marshal.c
$(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=ev_marshal $(srcdir)/ev-marshal.list --body >> ev-marshal.c
DISTCLEANFILES= \
ev-application-service.h
DISTCLEANFILES= \
ev-application-service.h \
ev-daemon-service.h
ev-application-service.h: $(srcdir)/ev-application-service.xml
$(AM_V_GEN)dbus-binding-tool --prefix=ev_application --mode=glib-server --output=ev-application-service.h $(srcdir)/ev-application-service.xml
ev-daemon-service.h: $(srcdir)/ev-daemon-service.xml
$(AM_V_GEN)dbus-binding-tool --prefix=ev_daemon --mode=glib-server --output=ev-daemon-service.h $(srcdir)/ev-daemon-service.xml
......@@ -5,12 +5,6 @@
<interface name="org.gnome.evince.Application">
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="ev_application"/>
<method name="OpenWindow">
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="ev_application_open_window"/>
<arg type="a{sv}" name="args" direction="in"/>
<arg type="u" name="timestamp" direction="in"/>
</method>
<method name="OpenURI">
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="ev_application_open_uri"/>
<arg type="s" name="uri" direction="in"/>
......
This diff is collapsed.
......@@ -55,15 +55,9 @@ void ev_application_shutdown (EvApplication *application);
gboolean ev_application_load_session (EvApplication *application,
const gchar **files);
gboolean ev_application_open_window (EvApplication *application,
GHashTable *args,
guint32 timestamp,
GError **error);
gboolean ev_application_open_uri (EvApplication *application,
const char *uri,
GHashTable *args,
guint timestamp,
GError **error);
void ev_application_open_window (EvApplication *application,
GdkScreen *screen,
guint32 timestamp);
void ev_application_open_uri_at_dest (EvApplication *application,
const char *uri,
GdkScreen *screen,
......@@ -94,7 +88,7 @@ GtkPageSetup *ev_application_get_page_setup (EvApplication *applicati
void ev_application_set_page_setup (EvApplication *application,
GtkPageSetup *page_setup);
const gchar *ev_application_get_dot_dir (EvApplication *application);
const gchar *ev_application_get_data_dir (EvApplication *application);
const gchar *ev_application_get_data_dir (EvApplication *application);
G_END_DECLS
......
<?xml version="1.0" encoding="UTF-8" ?>
<node name="/org/gnome/evince/Daemon">
<interface name="org.gnome.evince.Daemon">
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="ev_daemon"/>
<method name="RegisterDocument">
<annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
<arg type="s" name="uri" direction="in"/>
<arg type="s" name="owner" direction="out"/>
</method>
<method name="UnregisterDocument">
<annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
<arg type="s" name="uri" direction="in"/>
</method>
</interface>
</node>
/* ev-metadata.c
* this file is part of evince, a gnome document viewer
*
* Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
*
* Evince 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.
*
* Evince 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 "config.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dbus/dbus-glib-bindings.h>
#include <dbus/dbus-glib-lowlevel.h>
#define EV_DBUS_DAEMON_NAME "org.gnome.evince.Daemon"
#define EV_DBUS_DAEMON_OBJECT_PATH "/org/gnome/evince/Daemon"
#define EV_TYPE_DAEMON (ev_daemon_get_type ())
#define EV_DAEMON(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EV_TYPE_DAEMON, EvDaemon))
#define EV_DAEMON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EV_TYPE_DAEMON, EvDaemonClass))
#define EV_IS_DAEMON(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EV_TYPE_DAEMON))
typedef struct _EvDaemon EvDaemon;
typedef struct _EvDaemonClass EvDaemonClass;
struct _EvDaemon {
GObject base;
DBusGProxy *bus_proxy;
GList *docs;
guint n_docs;
guint timer_id;
};
struct _EvDaemonClass {
GObjectClass base_class;
};
static GType ev_daemon_get_type (void) G_GNUC_CONST;
static gboolean ev_daemon_register_document (EvDaemon *ev_daemon,
const gchar *uri,
DBusGMethodInvocation *context);
static gboolean ev_daemon_unregister_document (EvDaemon *ev_daemon,
const gchar *uri,
DBusGMethodInvocation *context);
#include "ev-daemon-service.h"
static EvDaemon *ev_daemon = NULL;
G_DEFINE_TYPE(EvDaemon, ev_daemon, G_TYPE_OBJECT)
typedef struct {
gchar *dbus_name;
gchar *uri;
} EvDoc;
static void
ev_doc_free (EvDoc *doc)
{
if (!doc)
return;
g_free (doc->dbus_name);
g_free (doc->uri);
g_free (doc);
}
static EvDoc *
ev_daemon_find_doc (EvDaemon *ev_daemon,
const gchar *uri)
{
GList *l;
for (l = ev_daemon->docs; l; l = g_list_next (l)) {
EvDoc *doc = (EvDoc *)l->data;
if (strcmp (doc->uri, uri) == 0)
return doc;
}
return NULL;
}
static void
ev_daemon_finalize (GObject *object)
{
EvDaemon *ev_daemon = EV_DAEMON (object);
if (ev_daemon->docs) {
g_list_foreach (ev_daemon->docs, (GFunc)ev_doc_free, NULL);
g_list_free (ev_daemon->docs);
ev_daemon->docs = NULL;
}
if (ev_daemon->bus_proxy) {
g_object_unref (ev_daemon->bus_proxy);
ev_daemon->bus_proxy = NULL;
}
G_OBJECT_CLASS (ev_daemon_parent_class)->finalize (object);
}
static void
ev_daemon_init (EvDaemon *ev_daemon)
{
}
static void
ev_daemon_class_init (EvDaemonClass *klass)
{
GObjectClass *g_object_class = G_OBJECT_CLASS (klass);
g_object_class->finalize = ev_daemon_finalize;
dbus_g_object_type_install_info (EV_TYPE_DAEMON,
&dbus_glib_ev_daemon_object_info);
}
static gboolean
ev_daemon_shutdown (EvDaemon *ev_daemon)
{
g_object_unref (ev_daemon);
return FALSE;
}
static void
ev_daemon_stop_killtimer (EvDaemon *ev_daemon)
{
if (ev_daemon->timer_id > 0)
g_source_remove (ev_daemon->timer_id);
ev_daemon->timer_id = 0;
}
static void
ev_daemon_start_killtimer (EvDaemon *ev_daemon)
{
ev_daemon_stop_killtimer (ev_daemon);
ev_daemon->timer_id =
g_timeout_add_seconds (30,
(GSourceFunc) ev_daemon_shutdown,
ev_daemon);
}
static void
ev_daemon_name_owner_changed (DBusGProxy *proxy,
const gchar *name,
const gchar *old_owner,
const gchar *new_owner,
EvDaemon *ev_daemon)
{
GList *l, *next = NULL;
if (*name == ':' && *new_owner == '\0') {
for (l = ev_daemon->docs; l; l = next) {
EvDoc *doc = (EvDoc *)l->data;
next = l->next;
if (strcmp (doc->dbus_name, name) == 0) {
ev_doc_free (doc);
ev_daemon->docs = g_list_delete_link (ev_daemon->docs, l);
if (--ev_daemon->n_docs == 0)
ev_daemon_start_killtimer (ev_daemon);
}
}
}
}
static EvDaemon *
ev_daemon_get (void)
{
DBusGConnection *connection;
guint request_name_result;
GError *error = NULL;
if (ev_daemon)
return ev_daemon;
connection = dbus_g_bus_get (DBUS_BUS_STARTER, &error);
if (!connection) {
g_printerr ("Failed to connect to the D-BUS daemon: %s\n", error->message);
g_error_free (error);
return NULL;
}
ev_daemon = g_object_new (EV_TYPE_DAEMON, NULL);
ev_daemon->bus_proxy = dbus_g_proxy_new_for_name (connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS);
if (!org_freedesktop_DBus_request_name (ev_daemon->bus_proxy,
EV_DBUS_DAEMON_NAME,
DBUS_NAME_FLAG_DO_NOT_QUEUE,
&request_name_result, &error)) {
g_printerr ("Failed to acquire daemon name: %s", error->message);
g_error_free (error);
g_object_unref (ev_daemon);
return NULL;
}
switch (request_name_result) {
case DBUS_REQUEST_NAME_REPLY_EXISTS:
g_printerr ("Evince daemon already running, exiting.\n");
g_object_unref (ev_daemon);
return NULL;
case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
dbus_g_connection_register_g_object (connection,
EV_DBUS_DAEMON_OBJECT_PATH,
G_OBJECT (ev_daemon));
break;
default:
g_printerr ("Not primary owner of the service, exiting.\n");
g_object_unref (ev_daemon);
return NULL;
}
dbus_g_proxy_add_signal (ev_daemon->bus_proxy,
"NameOwnerChanged",
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_INVALID);
dbus_g_proxy_connect_signal (ev_daemon->bus_proxy, "NameOwnerChanged",
G_CALLBACK (ev_daemon_name_owner_changed),
ev_daemon, NULL);
ev_daemon_start_killtimer (ev_daemon);
return ev_daemon;
}
static gboolean
ev_daemon_register_document (EvDaemon *ev_daemon,
const gchar *uri,
DBusGMethodInvocation *method)
{
EvDoc *doc;
const gchar *owner = NULL;
doc = ev_daemon_find_doc (ev_daemon, uri);
if (doc) {
/* Already registered */
owner = doc->dbus_name;
} else {
doc = g_new (EvDoc, 1);
doc->dbus_name = dbus_g_method_get_sender (method);
doc->uri = g_strdup (uri);
ev_daemon->docs = g_list_prepend (ev_daemon->docs, doc);
if (ev_daemon->n_docs++ == 0)
ev_daemon_stop_killtimer (ev_daemon);
}
dbus_g_method_return (method, owner);
return TRUE;
}
static gboolean
ev_daemon_unregister_document (EvDaemon *ev_daemon,
const gchar *uri,
DBusGMethodInvocation *method)
{
EvDoc *doc;
gchar *sender;
doc = ev_daemon_find_doc (ev_daemon, uri);
if (!doc) {
g_warning ("Document %s is not registered\n", uri);
dbus_g_method_return (method);
return TRUE;
}
sender = dbus_g_method_get_sender (method);
if (strcmp (doc->dbus_name, sender) != 0) {
g_warning ("Failed to unregister document %s: invalid owner %s, expected %s\n",
uri, sender, doc->dbus_name);
g_free (sender);
dbus_g_method_return (method);
return TRUE;
}
g_free (sender);
ev_daemon->docs = g_list_remove (ev_daemon->docs, doc);
ev_doc_free (doc);
if (--ev_daemon->n_docs == 0)
ev_daemon_start_killtimer (ev_daemon);
dbus_g_method_return (method);
return TRUE;
}
static void
do_exit (GMainLoop *loop,
GObject *object)
{
if (g_main_loop_is_running (loop))
g_main_loop_quit (loop);
}
static gboolean
convert_metadata (const gchar *metadata)
{
GFile *file;
gchar *cmd;
gint exit_status;
GFileAttributeInfoList *namespaces;
gboolean supported = FALSE;
GError *error = NULL;
gboolean retval;
/* If metadata is not supported for a local file
* is likely because and old gvfs version is running.
*/
file = g_file_new_for_path (metadata);
namespaces = g_file_query_writable_namespaces (file, NULL, NULL);
if (namespaces) {
gint i;
for (i = 0; i < namespaces->n_infos; i++) {
if (strcmp (namespaces->infos[i].name, "metadata") == 0) {
supported = TRUE;
break;
}
}
g_file_attribute_info_list_unref (namespaces);
}
if (!supported) {
g_warning ("%s\n",
"GVFS metadata not supported, "
"Evince will run without metadata support");
g_object_unref (file);
return FALSE;
}
g_object_unref (file);
cmd = g_strdup_printf ("%s %s", LIBEXECDIR"/evince-convert-metadata", metadata);
retval = g_spawn_command_line_sync (cmd, NULL, NULL, &exit_status, &error);
g_free (cmd);
if (!retval) {
g_printerr ("Error migrating metadata: %s\n", error->message);
g_error_free (error);
}
return retval && exit_status == 0;
}
static void
ev_migrate_metadata (void)
{
gchar *updated;
gchar *metadata;
gchar *dot_dir;
dot_dir = g_build_filename (g_get_home_dir (),
".gnome2",
"evince",
NULL);
updated = g_build_filename (dot_dir, "migrated-to-gvfs", NULL);
if (g_file_test (updated, G_FILE_TEST_EXISTS)) {
/* Already migrated */
g_free (updated);
g_free (dot_dir);
return;
}
metadata = g_build_filename (dot_dir, "ev-metadata.xml", NULL);
if (g_file_test (metadata, G_FILE_TEST_EXISTS)) {
if (convert_metadata (metadata)) {
gint fd;
fd = g_creat (updated, 0600);
if (fd != -1) {
close (fd);
}
}
}
g_free (dot_dir);
g_free (updated);
g_free (metadata);
}
gint
main (gint argc, gchar **argv)
{
GMainLoop *loop;
/* Init glib threads asap */
if (!g_thread_supported ())
g_thread_init (NULL);
g_type_init ();
if (!ev_daemon_get ())
return 1;
ev_migrate_metadata ();
loop = g_main_loop_new (NULL, FALSE);
g_object_weak_ref (G_OBJECT (ev_daemon),
(GWeakNotify) do_exit,
loop);
g_main_loop_run (loop);
g_main_loop_unref (loop);
return 0;
}
......@@ -26,20 +26,7 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#ifndef G_OS_WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif
#ifdef ENABLE_DBUS
#include <gdk/gdkx.h>
#include <dbus/dbus-glib-bindings.h>
#endif
#include "ev-application.h"
#include "ev-backends-manager.h"
#include "ev-debug.h"
#include "ev-init.h"
#include "ev-file-helpers.h"
......@@ -154,167 +141,6 @@ launch_previewer (void)
return retval;
}
#ifndef G_OS_WIN32
static gboolean
convert_metadata (const gchar *metadata)
{
GFile *file;
gchar *cmd;
gint exit_status;
GError *error = NULL;
gboolean retval;
/* If metadata is not supported for a local file
* is likely because and old gvfs version is running.
*/
file = g_file_new_for_path (metadata);
if (!ev_is_metadata_supported_for_file (file)) {
g_warning ("%s\n",
"GVFS metadata not supported, "
"Evince will run without metadata support");
g_object_unref (file);
return FALSE;
}
g_object_unref (file);
cmd = g_strdup_printf ("%s %s", LIBEXECDIR"/evince-convert-metadata", metadata);
retval = g_spawn_command_line_sync (cmd, NULL, NULL, &exit_status, &error);
g_free (cmd);
if (!retval) {
g_printerr ("Error migrating metadata: %s\n", error->message);
g_error_free (error);
}
return retval && exit_status == 0;
}
static void
ev_migrate_metadata (void)
{
gchar *updated;
gchar *metadata;
updated = g_build_filename (ev_application_get_dot_dir (EV_APP),
"migrated-to-gvfs", NULL);
if (g_file_test (updated, G_FILE_TEST_EXISTS)) {
/* Already migrated */
g_free (updated);
return;
}