ev-application.c 40.8 KB
Newer Older
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
1
2
3
/* this file is part of evince, a gnome document viewer
 *
 *  Copyright (C) 2004 Martin Kretzschmar
4
 *  Copyright © 2010, 2012 Christian Persch
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 *  Author:
 *    Martin Kretzschmar <martink@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
Arun Persaud's avatar
Arun Persaud committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
22
23
 */

24

25
#include <config.h>
26
#include <stdlib.h>
27
28
#include <string.h>

29
30
#include <glib.h>
#include <glib/gi18n.h>
31
#include <glib/gstdio.h>
32
#include <gtk/gtk.h>
33
#ifdef GDK_WINDOWING_X11
34
#include <gdk/gdkx.h>
35
#endif
36
#include <unistd.h>
37

Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
38
#include "ev-application.h"
39
#include "ev-file-helpers.h"
40
#include "ev-stock-icons.h"
41

42
#ifdef ENABLE_DBUS
43
#include "ev-gdbus-generated.h"
44
45
#include "ev-media-player-keys.h"
#endif /* ENABLE_DBUS */
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
46

47
struct _EvApplication {
48
	GtkApplication base_instance;
49

50
	gchar *uri;
51

52
	gchar *dot_dir;
53
54

#ifdef ENABLE_DBUS
55
        EvEvinceApplication *skeleton;
56
	EvMediaPlayerKeys *keys;
57
	gboolean doc_registered;
58
#endif
59
60
61
};

struct _EvApplicationClass {
62
	GtkApplicationClass base_class;
63
64
};

65
G_DEFINE_TYPE (EvApplication, ev_application, GTK_TYPE_APPLICATION)
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
66

67
#ifdef ENABLE_DBUS
Marek Kašík's avatar
Marek Kašík committed
68
#define APPLICATION_DBUS_NAME        "org.gnome.evince.Application"
69
70
#define APPLICATION_DBUS_OBJECT_PATH "/org/gnome/evince/Evince"
#define APPLICATION_DBUS_INTERFACE   "org.gnome.evince.Application"
71
72
73
74

#define EVINCE_DAEMON_SERVICE        "org.gnome.evince.Daemon"
#define EVINCE_DAEMON_OBJECT_PATH    "/org/gnome/evince/Daemon"
#define EVINCE_DAEMON_INTERFACE      "org.gnome.evince.Daemon"
75
#endif
76

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
static void _ev_application_open_uri_at_dest (EvApplication  *application,
					      const gchar    *uri,
					      GdkScreen      *screen,
					      EvLinkDest     *dest,
					      EvWindowRunMode mode,
					      const gchar    *search_string,
					      guint           timestamp);
static void ev_application_open_uri_in_window (EvApplication  *application,
					       const char     *uri,
					       EvWindow       *ev_window,
					       GdkScreen      *screen,
					       EvLinkDest     *dest,
					       EvWindowRunMode mode,
					       const gchar    *search_string,
					       guint           timestamp);

93
/**
94
 * ev_application_new:
95
 *
96
 * Creates a new #EvApplication instance.
97
 *
98
 * Returns: (transfer full): a newly created #EvApplication
99
 */
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
100
EvApplication *
101
ev_application_new (void)
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
102
{
103
  const GApplicationFlags flags = G_APPLICATION_NON_UNIQUE;
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
104

105
  return g_object_new (EV_TYPE_APPLICATION,
Marek Kašík's avatar
Marek Kašík committed
106
                       "application-id", APPLICATION_DBUS_NAME,
107
108
                       "flags", flags,
                       NULL);
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
109
110
}

Hib Eris's avatar
Hib Eris committed
111
#ifdef ENABLE_DBUS
112
113
114
115
116
117
118
119
120
/**
 * ev_display_open_if_needed:
 * @name: the name of the display to be open if it's needed.
 *
 * Search among all the open displays if any of them have the same name as the
 * passed name. If the display isn't found it tries the open it.
 *
 * Returns: a #GdkDisplay of the display with the passed name.
 */
121
122
123
124
125
126
127
128
129
130
131
132
static GdkDisplay *
ev_display_open_if_needed (const gchar *name)
{
	GSList     *displays;
	GSList     *l;
	GdkDisplay *display = NULL;

	displays = gdk_display_manager_list_displays (gdk_display_manager_get ());

	for (l = displays; l != NULL; l = l->next) {
		const gchar *display_name = gdk_display_get_name ((GdkDisplay *) l->data);

133
134
135
136
                /* The given name might come with the screen number, because GdkAppLaunchContext
                 * uses gdk_screen_make_display_name().
                 */
                if (g_str_has_prefix (name, display_name)) {
137
138
139
140
141
142
143
144
145
			display = l->data;
			break;
		}
	}

	g_slist_free (displays);

	return display != NULL ? display : gdk_display_open (name);
}
Hib Eris's avatar
Hib Eris committed
146
#endif
147

148
149
150
151
152
static void
ev_spawn (const char     *uri,
	  GdkScreen      *screen,
	  EvLinkDest     *dest,
	  EvWindowRunMode mode,
153
154
	  const gchar    *search_string,
	  guint           timestamp)
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
155
{
156
157
158
	GString *cmd;
	gchar *path, *cmdline;
	GAppInfo *app;
159
	GError  *error = NULL;
160

161
162
	cmd = g_string_new (NULL);

Hib Eris's avatar
Hib Eris committed
163
164
165
166
167
#ifdef G_OS_WIN32
{
	gchar *dir;

	dir = g_win32_get_package_installation_directory_of_module (NULL);
168
169
	path = g_build_filename (dir, "bin", "evince", NULL);

Hib Eris's avatar
Hib Eris committed
170
171
172
	g_free (dir);
}
#else
173
	path = g_build_filename (BINDIR, "evince", NULL);
Hib Eris's avatar
Hib Eris committed
174
#endif
175

176
177
178
	g_string_append_printf (cmd, " %s", path);
	g_free (path);
	
179
180
	/* Page label */
	if (dest) {
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
                switch (ev_link_dest_get_dest_type (dest)) {
                case EV_LINK_DEST_TYPE_PAGE_LABEL:
                        g_string_append_printf (cmd, " --page-label=%s",
                                                ev_link_dest_get_page_label (dest));
                        break;
                case EV_LINK_DEST_TYPE_PAGE:
                        g_string_append_printf (cmd, " --page-index=%d",
                                                ev_link_dest_get_page (dest) + 1);
                        break;
                case EV_LINK_DEST_TYPE_NAMED:
                        g_string_append_printf (cmd, " --named-dest=%s",
                                                ev_link_dest_get_named_dest (dest));
                        break;
                default:
                        break;
                }
197
198
	}

199
200
	/* Find string */
	if (search_string) {
201
		g_string_append_printf (cmd, " --find=%s", search_string);
202
	}
203

204
205
206
	/* Mode */
	switch (mode) {
	case EV_WINDOW_MODE_FULLSCREEN:
207
		g_string_append (cmd, " -f");
208
209
		break;
	case EV_WINDOW_MODE_PRESENTATION:
210
		g_string_append (cmd, " -s");
211
212
213
214
215
		break;
	default:
		break;
	}

216
	cmdline = g_string_free (cmd, FALSE);
217
	app = g_app_info_create_from_commandline (cmdline, NULL, G_APP_INFO_CREATE_SUPPORTS_URIS, &error);
218
219

	if (app != NULL) {
220
                GList uri_list;
221
                GList *uris = NULL;
Hib Eris's avatar
Hib Eris committed
222
		GdkAppLaunchContext *ctx;
223

224
225
226
227
		ctx = gdk_display_get_app_launch_context (gdk_screen_get_display (screen));
		gdk_app_launch_context_set_screen (ctx, screen);
		gdk_app_launch_context_set_timestamp (ctx, timestamp);

228
229
230
231
232
233
                /* Some URIs can be changed when passed through a GFile
                 * (for instance unsupported uris with strange formats like mailto:),
                 * so if you have a textual uri you want to pass in as argument,
                 * consider using g_app_info_launch_uris() instead.
                 * See https://bugzilla.gnome.org/show_bug.cgi?id=644604
                 */
234
235
236
237
238
239
                if (uri) {
                        uri_list.data = (gchar *)uri;
                        uri_list.prev = uri_list.next = NULL;
                        uris = &uri_list;
                }
		g_app_info_launch_uris (app, uris, G_APP_LAUNCH_CONTEXT (ctx), &error);
240

241
242
243
244
245
		g_object_unref (app);
		g_object_unref (ctx);
	}

	if (error != NULL) {
246
		g_printerr ("Error launching evince %s: %s\n", uri, error->message);
247
		g_error_free (error);
248
	}
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
249

250
	g_free (cmdline);
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
251
252
}

253
254
255
256
257
static EvWindow *
ev_application_get_empty_window (EvApplication *application,
				 GdkScreen     *screen)
{
	EvWindow *empty_window = NULL;
258
	GList    *windows, *l;
259

260
        windows = gtk_application_get_windows (GTK_APPLICATION (application));
261
	for (l = windows; l != NULL; l = l->next) {
262
263
264
265
266
267
		EvWindow *window;

                if (!EV_IS_WINDOW (l->data))
                          continue;

                window = EV_WINDOW (l->data);
268
269
270
271
272
273
274
275
276
277
278
279

		if (ev_window_is_empty (window) &&
		    gtk_window_get_screen (GTK_WINDOW (window)) == screen) {
			empty_window = window;
			break;
		}
	}

	return empty_window;
}


280
#ifdef ENABLE_DBUS
281
282
283
284
285
286
287
288
289
290
291
typedef struct {
	gchar          *uri;
	GdkScreen      *screen;
	EvLinkDest     *dest;
	EvWindowRunMode mode;
	gchar          *search_string;
	guint           timestamp;
} EvRegisterDocData;

static void
ev_register_doc_data_free (EvRegisterDocData *data)
292
{
293
294
	if (!data)
		return;
295

296
297
298
299
300
301
302
303
304
305
	g_free (data->uri);
	if (data->search_string)
		g_free (data->search_string);
	if (data->dest)
		g_object_unref (data->dest);

	g_free (data);
}

static void
306
307
308
on_reload_cb (GObject      *source_object,
	      GAsyncResult *res,
	      gpointer      user_data)
309
310
311
312
313
{
	GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
	GVariant        *value;
	GError          *error = NULL;

314
315
        g_application_release (g_application_get_default ());

316
	value = g_dbus_connection_call_finish (connection, res, &error);
317
318
319
320
	if (value != NULL) {
                g_variant_unref (value);
        } else {
		g_printerr ("Failed to Reload: %s\n", error->message);
321
322
323
		g_error_free (error);
	}

324
325
326
	/* We did not open a window, so manually clear the startup
	 * notification. */
	gdk_notify_startup_complete ();
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
}

static void
on_register_uri_cb (GObject      *source_object,
		    GAsyncResult *res,
		    gpointer      user_data)
{
	GDBusConnection   *connection = G_DBUS_CONNECTION (source_object);
	EvRegisterDocData *data = (EvRegisterDocData *)user_data;
	EvApplication     *application = EV_APP;
	GVariant          *value;
	const gchar       *owner;
	GVariantBuilder    builder;
	GError            *error = NULL;

342
343
        g_application_release (G_APPLICATION (application));

344
345
	value = g_dbus_connection_call_finish (connection, res, &error);
	if (!value) {
346
		g_printerr ("Error registering document: %s\n", error->message);
347
		g_error_free (error);
348
349
350
351
352
353
354
355
356
357
358

		_ev_application_open_uri_at_dest (application,
						  data->uri,
						  data->screen,
						  data->dest,
						  data->mode,
						  data->search_string,
						  data->timestamp);
		ev_register_doc_data_free (data);

		return;
359
360
	}

361
	g_variant_get (value, "(&s)", &owner);
362

363
	/* This means that the document wasn't already registered; go
364
365
366
         * ahead with opening it.
         */
	if (owner[0] == '\0') {
367
                g_variant_unref (value);
368
369
370
371
372
373
374
375
376
377
378
379
380

		application->doc_registered = TRUE;

		_ev_application_open_uri_at_dest (application,
						  data->uri,
						  data->screen,
						  data->dest,
						  data->mode,
						  data->search_string,
						  data->timestamp);
		ev_register_doc_data_free (data);

                return;
381
        }
382

383
	/* Already registered */
384
	g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a{sv}u)"));
385
386
387
        g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
        g_variant_builder_add (&builder, "{sv}",
                               "display",
388
389
                               g_variant_new_string (gdk_display_get_name (gdk_screen_get_display (data->screen))));
	if (data->dest) {
390
391
392
393
394
395
396
                switch (ev_link_dest_get_dest_type (data->dest)) {
                case EV_LINK_DEST_TYPE_PAGE_LABEL:
                        g_variant_builder_add (&builder, "{sv}", "page-label",
                                               g_variant_new_string (ev_link_dest_get_page_label (data->dest)));
                        break;
                case EV_LINK_DEST_TYPE_PAGE:
                        g_variant_builder_add (&builder, "{sv}", "page-index",
397
                                               g_variant_new_uint32 (ev_link_dest_get_page (data->dest)));
398
399
400
401
402
403
404
                        break;
                case EV_LINK_DEST_TYPE_NAMED:
                        g_variant_builder_add (&builder, "{sv}", "named-dest",
                                               g_variant_new_string (ev_link_dest_get_named_dest (data->dest)));
                        break;
                default:
                        break;
405
                }
406
	}
407
	if (data->search_string) {
408
409
                g_variant_builder_add (&builder, "{sv}",
                                       "find-string",
410
                                       g_variant_new_string (data->search_string));
411
	}
412
	if (data->mode != EV_WINDOW_MODE_NORMAL) {
413
414
                g_variant_builder_add (&builder, "{sv}",
                                       "mode",
415
                                       g_variant_new_uint32 (data->mode));
416
417
418
	}
        g_variant_builder_close (&builder);

419
420
421
422
423
424
        g_variant_builder_add (&builder, "u", data->timestamp);

        g_dbus_connection_call (connection,
				owner,
				APPLICATION_DBUS_OBJECT_PATH,
				APPLICATION_DBUS_INTERFACE,
425
				"Reload",
426
427
428
429
430
				g_variant_builder_end (&builder),
				NULL,
				G_DBUS_CALL_FLAGS_NONE,
				-1,
				NULL,
431
				on_reload_cb,
432
				NULL);
433
        g_application_hold (G_APPLICATION (application));
434
	g_variant_unref (value);
435
436
	ev_register_doc_data_free (data);
}
437

438
439
/*
 * ev_application_register_uri:
440
441
442
443
444
445
446
 * @application: The instance of the application.
 * @uri: The uri to be opened.
 * @screen: The screen where the link will be shown.
 * @dest: The #EvLinkDest of the document.
 * @mode: The run mode of the window.
 * @search_string: The word or phrase to find in the document.
 * @timestamp: Current time value.
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
 *
 * Registers @uri with evince-daemon.
 *
 */
static void
ev_application_register_uri (EvApplication  *application,
			     const gchar    *uri,
                             GdkScreen      *screen,
                             EvLinkDest     *dest,
                             EvWindowRunMode mode,
                             const gchar    *search_string,
			     guint           timestamp)
{
	EvRegisterDocData *data;

462
463
464
	/* If connection hasn't been made fall back to opening without D-BUS features */
	if (!application->skeleton) {
		_ev_application_open_uri_at_dest (application, uri, screen, dest, mode, search_string, timestamp);
465
		return;
466
	}
467
468
469
470
471

	if (application->doc_registered) {
		/* Already registered, reload */
		GList *windows, *l;

472
		windows = gtk_application_get_windows (GTK_APPLICATION (application));
473
		for (l = windows; l != NULL; l = g_list_next (l)) {
474
475
                        if (!EV_IS_WINDOW (l->data))
                                continue;
476

477
478
			ev_application_open_uri_in_window (application, uri,
                                                           EV_WINDOW (l->data),
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
							   screen, dest, mode,
							   search_string,
							   timestamp);
		}

		return;
	}

	data = g_new (EvRegisterDocData, 1);
	data->uri = g_strdup (uri);
	data->screen = screen;
	data->dest = dest ? g_object_ref (dest) : NULL;
	data->mode = mode;
	data->search_string = search_string ? g_strdup (search_string) : NULL;
	data->timestamp = timestamp;

495
        g_dbus_connection_call (g_application_get_dbus_connection (G_APPLICATION (application)),
496
497
498
499
500
501
502
503
504
505
506
				EVINCE_DAEMON_SERVICE,
				EVINCE_DAEMON_OBJECT_PATH,
				EVINCE_DAEMON_INTERFACE,
				"RegisterDocument",
				g_variant_new ("(s)", uri),
				G_VARIANT_TYPE ("(s)"),
				G_DBUS_CALL_FLAGS_NONE,
				-1,
				NULL,
				on_register_uri_cb,
				data);
507
508

        g_application_hold (G_APPLICATION (application));
509
510
511
512
513
514
}

static void
ev_application_unregister_uri (EvApplication *application,
			       const gchar   *uri)
{
515
        GVariant *value;
516
	GError   *error = NULL;
517

518
	if (!application->doc_registered)
519
520
		return;

521
522
523
524
	/* This is called from ev_application_shutdown(),
	 * so it's safe to use the sync api
	 */
        value = g_dbus_connection_call_sync (
525
		g_application_get_dbus_connection (G_APPLICATION (application)),
526
527
528
529
530
531
532
533
534
535
		EVINCE_DAEMON_SERVICE,
		EVINCE_DAEMON_OBJECT_PATH,
		EVINCE_DAEMON_INTERFACE,
		"UnregisterDocument",
		g_variant_new ("(s)", uri),
		NULL,
		G_DBUS_CALL_FLAGS_NO_AUTO_START,
		-1,
		NULL,
		&error);
536
        if (value == NULL) {
537
		g_printerr ("Error unregistering document: %s\n", error->message);
538
		g_error_free (error);
539
540
	} else {
                g_variant_unref (value);
541
542
543
544
545
546
547
	}
}
#endif /* ENABLE_DBUS */

static void
ev_application_open_uri_in_window (EvApplication  *application,
				   const char     *uri,
548
				   EvWindow       *ev_window,
549
550
551
552
553
554
				   GdkScreen      *screen,
				   EvLinkDest     *dest,
				   EvWindowRunMode mode,
				   const gchar    *search_string,
				   guint           timestamp)
{
555
556
557
558
#ifdef GDK_WINDOWING_X11
	GdkWindow *gdk_window;
#endif

559
560
561
        if (uri == NULL)
                uri = application->uri;

562
563
564
565
566
567
568
569
570
	if (screen) {
		ev_stock_icons_set_screen (screen);
		gtk_window_set_screen (GTK_WINDOW (ev_window), screen);
	}

	/* We need to load uri before showing the window, so
	   we can restore window size without flickering */
	ev_window_open_uri (ev_window, uri, dest, mode, search_string);

571
	if (!gtk_widget_get_realized (GTK_WIDGET (ev_window)))
572
573
574
		gtk_widget_realize (GTK_WIDGET (ev_window));

#ifdef GDK_WINDOWING_X11
575
	gdk_window = gtk_widget_get_window (GTK_WIDGET (ev_window));
576
577
578
579
	if (GDK_IS_X11_WINDOW (gdk_window)) {
		if (timestamp <= 0)
			timestamp = gdk_x11_get_server_time (gdk_window);
		gdk_x11_window_set_user_time (gdk_window, timestamp);
580

581
582
		gtk_window_present (GTK_WINDOW (ev_window));
	} else
583
#endif /* GDK_WINDOWING_X11 */
584
585
586
	{
		gtk_window_present_with_time (GTK_WINDOW (ev_window), timestamp);
	}
587
588
}

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
static void
_ev_application_open_uri_at_dest (EvApplication  *application,
				  const gchar    *uri,
				  GdkScreen      *screen,
				  EvLinkDest     *dest,
				  EvWindowRunMode mode,
				  const gchar    *search_string,
				  guint           timestamp)
{
	EvWindow *ev_window;

	ev_window = ev_application_get_empty_window (application, screen);
	if (!ev_window)
		ev_window = EV_WINDOW (ev_window_new ());

	ev_application_open_uri_in_window (application, uri, ev_window,
					   screen, dest, mode,
					   search_string,
					   timestamp);
}

610
611
612
613
614
615
616
/**
 * ev_application_open_uri_at_dest:
 * @application: The instance of the application.
 * @uri: The uri to be opened.
 * @screen: Thee screen where the link will be shown.
 * @dest: The #EvLinkDest of the document.
 * @mode: The run mode of the window.
617
 * @search_string: The word or phrase to find in the document.
618
619
620
621
622
623
624
625
 * @timestamp: Current time value.
 */
void
ev_application_open_uri_at_dest (EvApplication  *application,
				 const char     *uri,
				 GdkScreen      *screen,
				 EvLinkDest     *dest,
				 EvWindowRunMode mode,
626
				 const gchar    *search_string,
627
628
629
				 guint           timestamp)
{
	g_return_if_fail (uri != NULL);
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
630

631
632
633
634
	if (application->uri && strcmp (application->uri, uri) != 0) {
		/* spawn a new evince process */
		ev_spawn (uri, screen, dest, mode, search_string, timestamp);
		return;
635
636
	} else if (!application->uri) {
		application->uri = g_strdup (uri);
637
	}
638

639
#ifdef ENABLE_DBUS
640
	/* Register the uri or send Reload to
641
642
643
644
645
646
	 * remote instance if already registered
	 */
	ev_application_register_uri (application, uri, screen, dest, mode, search_string, timestamp);
#else
	_ev_application_open_uri_at_dest (application, uri, screen, dest, mode, search_string, timestamp);
#endif /* ENABLE_DBUS */
647
648
}

649
void
650
651
652
653
654
655
656
657
ev_application_new_window (EvApplication *application,
			   GdkScreen     *screen,
			   guint32        timestamp)
{
        /* spawn an empty window */
	ev_spawn (NULL, screen, NULL, EV_WINDOW_MODE_NORMAL, NULL, timestamp);
}

658
/**
659
 * ev_application_open_recent_view:
660
661
662
 * @application: The instance of the application.
 * @timestamp: Current time value.
 *
663
 * Creates a new window showing the recent view
664
665
 */
void
666
667
668
ev_application_open_recent_view (EvApplication *application,
                                 GdkScreen     *screen,
                                 guint32        timestamp)
669
670
{
	GtkWidget *new_window = ev_window_new ();
671
672
673

	ev_window_open_recent_view (EV_WINDOW (new_window));

674
675
676
#ifdef GDK_WINDOWING_X11
	GdkWindow *gdk_window;
#endif
677
678
679
680
681
682

	if (screen) {
		ev_stock_icons_set_screen (screen);
		gtk_window_set_screen (GTK_WINDOW (new_window), screen);
	}

683
	if (!gtk_widget_get_realized (new_window))
684
685
686
		gtk_widget_realize (new_window);

#ifdef GDK_WINDOWING_X11
687
	gdk_window = gtk_widget_get_window (GTK_WIDGET (new_window));
688
689
690
691
	if (GDK_IS_X11_WINDOW (gdk_window)) {
		if (timestamp <= 0)
			timestamp = gdk_x11_get_server_time (gdk_window);
		gdk_x11_window_set_user_time (gdk_window, timestamp);
692

693
694
		gtk_window_present (GTK_WINDOW (new_window));
	} else
695
#endif /* GDK_WINDOWING_X11 */
696
697
698
	{
		gtk_window_present_with_time (GTK_WINDOW (new_window), timestamp);
	}
699
700
}

701
#ifdef ENABLE_DBUS
702
703
704
705
static gboolean
handle_get_window_list_cb (EvEvinceApplication   *object,
                           GDBusMethodInvocation *invocation,
                           EvApplication         *application)
706
{
707
708
        GList     *windows, *l;
        GPtrArray *paths;
709

710
        paths = g_ptr_array_new ();
711

712
        windows = gtk_application_get_windows (GTK_APPLICATION (application));
713
        for (l = windows; l; l = g_list_next (l)) {
714
715
                if (!EV_IS_WINDOW (l->data))
                        continue;
716

717
                g_ptr_array_add (paths, (gpointer) ev_window_get_dbus_object_path (EV_WINDOW (l->data)));
718
        }
719

720
721
722
        g_ptr_array_add (paths, NULL);
        ev_evince_application_complete_get_window_list (object, invocation,
                                                        (const char * const *) paths->pdata);
723

724
        g_ptr_array_free (paths, TRUE);
725

726
727
        return TRUE;
}
728

729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
static gboolean
handle_reload_cb (EvEvinceApplication   *object,
                  GDBusMethodInvocation *invocation,
                  GVariant              *args,
                  guint                  timestamp,
                  EvApplication         *application)
{
        GList           *windows, *l;
        GVariantIter     iter;
        const gchar     *key;
        GVariant        *value;
        GdkDisplay      *display = NULL;
        EvLinkDest      *dest = NULL;
        EvWindowRunMode  mode = EV_WINDOW_MODE_NORMAL;
        const gchar     *search_string = NULL;
        GdkScreen       *screen = NULL;

        g_variant_iter_init (&iter, args);

        while (g_variant_iter_loop (&iter, "{&sv}", &key, &value)) {
                if (strcmp (key, "display") == 0 && g_variant_classify (value) == G_VARIANT_CLASS_STRING) {
                        display = ev_display_open_if_needed (g_variant_get_string (value, NULL));
                } else if (strcmp (key, "mode") == 0 && g_variant_classify (value) == G_VARIANT_CLASS_UINT32) {
                        mode = g_variant_get_uint32 (value);
                } else if (strcmp (key, "page-label") == 0 && g_variant_classify (value) == G_VARIANT_CLASS_STRING) {
                        dest = ev_link_dest_new_page_label (g_variant_get_string (value, NULL));
                } else if (strcmp (key, "named-dest") == 0 && g_variant_classify (value) == G_VARIANT_CLASS_STRING) {
                        dest = ev_link_dest_new_named (g_variant_get_string (value, NULL));
                } else if (strcmp (key, "page-index") == 0 && g_variant_classify (value) == G_VARIANT_CLASS_UINT32) {
                        dest = ev_link_dest_new_page (g_variant_get_uint32 (value));
                } else if (strcmp (key, "find-string") == 0 && g_variant_classify (value) == G_VARIANT_CLASS_STRING) {
                        search_string = g_variant_get_string (value, NULL);
                }
        }
763

764
765
        if (display != NULL)
                screen = gdk_display_get_default_screen (display);
766
767
768
        else
                screen = gdk_screen_get_default ();

769
        windows = gtk_application_get_windows (GTK_APPLICATION ((application)));
770
        for (l = windows; l != NULL; l = g_list_next (l)) {
771
772
                if (!EV_IS_WINDOW (l->data))
                        continue;
773
774

                ev_application_open_uri_in_window (application, NULL,
775
                                                   EV_WINDOW (l->data),
776
777
778
779
                                                   screen, dest, mode,
                                                   search_string,
                                                   timestamp);
        }
780

781
782
        if (dest)
                g_object_unref (dest);
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
783

784
        ev_evince_application_complete_reload (object, invocation);
785

786
787
        return TRUE;
}
788
789
#endif /* ENABLE_DBUS */

Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
790
void
791
792
ev_application_open_uri_list (EvApplication *application,
			      GSList        *uri_list,
793
			      GdkScreen     *screen,
794
			      guint          timestamp)
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
795
{
796
797
798
	GSList *l;

	for (l = uri_list; l != NULL; l = l->next) {
799
		ev_application_open_uri_at_dest (application, (char *)l->data,
800
						 screen, NULL, 0, NULL,
801
						 timestamp);
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
802
	}
803
}
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
804

805
806
807
808
809
810
811
static void
ev_application_accel_map_save (EvApplication *application)
{
	gchar *accel_map_file;
	gchar *tmp_filename;
	gint   fd;

812
        accel_map_file = g_build_filename (application->dot_dir, "accels", NULL);
813
814
815
816
817
818
819
820
821
822
823
824
	tmp_filename = g_strdup_printf ("%s.XXXXXX", accel_map_file);

	fd = g_mkstemp (tmp_filename);
	if (fd == -1) {
		g_free (accel_map_file);
		g_free (tmp_filename);

		return;
	}
	gtk_accel_map_save_fd (fd);
	close (fd);

825
        g_mkdir_with_parents (application->dot_dir, 0700);
826
827
828
829
830
831
832
833
834
835
836
837
838
839
	if (g_rename (tmp_filename, accel_map_file) == -1) {
		/* FIXME: win32? */
		g_unlink (tmp_filename);
	}

	g_free (accel_map_file);
	g_free (tmp_filename);
}

static void
ev_application_accel_map_load (EvApplication *application)
{
	gchar *accel_map_file;

840
        accel_map_file = g_build_filename (application->dot_dir, "accels", NULL);
841
842
843
844
	gtk_accel_map_load (accel_map_file);
	g_free (accel_map_file);
}

845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
static void
ev_application_migrate_config_dir (EvApplication *application)
{
        const gchar        *userdir;
        gchar              *old_dot_dir;
        gchar              *old_accels;
        GError             *error;
        gint                i;
        gboolean            dir_created = FALSE;
        static const gchar *config_files[] = {
                "evince_toolbar.xml",
                "print-settings",
                NULL
        };

        userdir = g_getenv ("GNOME22_USER_DIR");
        if (userdir) {
                old_dot_dir = g_build_filename (userdir, "evince", NULL);
                old_accels = g_build_filename (userdir, "accels", "evince", NULL);
        } else {
                old_dot_dir = g_build_filename (g_get_home_dir (),
                                                ".gnome2",
                                                "evince",
                                                NULL);
                old_accels = g_build_filename (g_get_home_dir (),
                                               ".gnome2", "accels",
                                               "evince", NULL);
        }

        if (g_file_test (old_dot_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
                for (i = 0; config_files[i]; i++) {
                        gchar   *old_filename;
                        gchar   *new_filename;
                        GFile   *old_file;
                        GFile   *new_file;

                        old_filename = g_build_filename (old_dot_dir, config_files[i], NULL);
                        if (!g_file_test (old_filename, G_FILE_TEST_EXISTS)) {
                                g_free (old_filename);
                                continue;
                        }

                        if (!dir_created) {
                                g_mkdir_with_parents (application->dot_dir, 0700);
                                dir_created = TRUE;
                        }

                        new_filename = g_build_filename (application->dot_dir, config_files[i], NULL);
                        old_file = g_file_new_for_path (old_filename);
                        new_file = g_file_new_for_path (new_filename);

                        error = NULL;
                        g_file_move (old_file, new_file, 0, NULL, NULL, NULL, &error);
                        if (error) {
899
900
                                g_printerr ("Error migrating config file %s: %s\n",
                                            old_filename, error->message);
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
                                g_error_free (error);
                        }

                        g_free (old_filename);
                        g_free (new_filename);
                        g_object_unref (old_file);
                        g_object_unref (new_file);
                }
        }

        g_free (old_dot_dir);

        if (g_file_test (old_accels, G_FILE_TEST_EXISTS)) {
                gchar *new_accels;
                GFile *old_accels_file;
                GFile *new_accels_file;

                if (!dir_created)
                        g_mkdir_with_parents (application->dot_dir, 0700);

                new_accels = g_build_filename (application->dot_dir, "accels", NULL);
                old_accels_file = g_file_new_for_path (old_accels);
                new_accels_file = g_file_new_for_path (new_accels);

                error = NULL;
                g_file_move (old_accels_file, new_accels_file, 0, NULL, NULL, NULL, &error);
                if (error) {
928
929
                        g_printerr ("Error migrating accelerator specifications file %s: %s\n",
                                    old_accels, error->message);
930
931
932
933
934
935
936
937
938
939
940
                        g_error_free (error);
                }

                g_free (new_accels);
                g_object_unref (old_accels_file);
                g_object_unref (new_accels_file);
        }

        g_free (old_accels);
}

941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
static void
app_help_cb (GSimpleAction *action,
             GVariant      *parameter,
             gpointer       user_data)
{
        EvApplication *application = user_data;

        ev_application_show_help (application, NULL, NULL);
}

static void
app_about_cb (GSimpleAction *action,
              GVariant      *parameter,
              gpointer       user_data)
{
        EvApplication *application = user_data;

        const char *authors[] = {
                "Martin Kretzschmar <m_kretzschmar@gmx.net>",
                "Jonathan Blandford <jrb@gnome.org>",
                "Marco Pesenti Gritti <marco@gnome.org>",
                "Nickolay V. Shmyrev <nshmyrev@yandex.ru>",
                "Bryan Clark <clarkbw@gnome.org>",
                "Carlos Garcia Campos <carlosgc@gnome.org>",
                "Wouter Bolsterlee <wbolster@gnome.org>",
                "Christian Persch <chpe" "\100" "gnome.org>",
                "Germán Poo-Caamaño <gpoo" "\100" "gnome.org>",
                NULL
        };
        const char *documenters[] = {
                "Nickolay V. Shmyrev <nshmyrev@yandex.ru>",
                "Phil Bull <philbull@gmail.com>",
                "Tiffany Antpolski <tiffany.antopolski@gmail.com>",
                NULL
        };
#ifdef ENABLE_NLS
        const char **p;

        for (p = authors; *p; ++p)
                *p = _(*p);

        for (p = documenters; *p; ++p)
                *p = _(*p);
#endif

        gtk_show_about_dialog (gtk_application_get_active_window (GTK_APPLICATION (application)),
                               "name", _("Evince"),
                               "version", VERSION,
                               "copyright", _("© 1996–2017 The Evince authors"),
                               "license-type", GTK_LICENSE_GPL_2_0,
                               "website", "https://wiki.gnome.org/Apps/Evince",
                               "comments", _("Document Viewer"),
                               "authors", authors,
                               "documenters", documenters,
                               "translator-credits", _("translator-credits"),
                               "logo-icon-name", "evince",
                               NULL);
}

Christian Persch's avatar
Christian Persch committed
1000
1001
1002
static void
ev_application_startup (GApplication *gapplication)
{
1003
1004
1005
1006
1007
        const GActionEntry app_menu_actions[] = {
                { "help", app_help_cb, NULL, NULL, NULL },
                { "about", app_about_cb, NULL, NULL, NULL }
        };

1008
        const gchar *action_accels[] = {
1009
          "win.win",                    "<Ctrl>O", NULL,
1010
          "win.open-copy",              "<Ctrl>N", NULL,
1011
          "win.save-as",                "<Ctrl>S", NULL,
1012
          "win.print",                  "<Ctrl>P", NULL,
1013
          "win.show-properties",        "<alt>Return", NULL,
1014
1015
1016
1017
          "win.copy",                   "<Ctrl>C", "<Ctrl>Insert", NULL,
          "win.select-all",             "<Ctrl>A", NULL,
          "win.save-settings",          "<Ctrl>T", NULL,
          "win.add-bookmark",           "<Ctrl>D", NULL,
1018
          "win.delete-bookmark",        "<Ctrl><Shift>D", NULL,
1019
1020
1021
          "win.close",                  "<Ctrl>W", NULL,
          "win.escape",                 "Escape", NULL,
          "win.find",                   "<Ctrl>F", "slash", NULL,
1022
1023
          "win.find-next",              "<Ctrl>G", "F3", NULL,
          "win.find-previous",          "<Ctrl><Shift>G", "<Shift>F3", NULL,
1024
          "win.select-page",            "<Ctrl>L", NULL,
1025
          "win.go-backwards",           "<Shift>Page_Up", NULL,
1026
          "win.go-forward",             "<Shift>Page_Down", NULL,
1027
1028
          "win.go-next-page",           "n", "<Ctrl>Page_Down", NULL,
          "win.go-previous-page",       "p", "<Ctrl>Page_Up", NULL,
1029
1030
          "win.go-back-history",        "<alt>P", "Back", NULL,
          "win.go-forward-history",     "<alt>N", "Forward", NULL,
1031
1032
          "win.sizing-mode::fit-page",  "f", NULL,
          "win.sizing-mode::fit-width", "w", NULL,
1033
          "win.sizing-mode::automatic", "a", NULL,
1034
          "win.default-zoom",           "<Ctrl>0", NULL,
1035
1036
          "win.open-menu",              "F10", NULL,
          "win.caret-navigation",       "F7", NULL,
1037
          "win.zoom-in",                "plus", "<Ctrl>plus", "KP_Add", "<Ctrl>KP_Add", "equal", "<Ctrl>equal", NULL,
1038
1039
1040
          "win.zoom-out",               "minus", "<Ctrl>minus", "KP_Subtract", "<Ctrl>KP_Subtract", NULL,
          "win.show-side-pane",         "F9", NULL,
          "win.fullscreen",             "F11", NULL,
1041
          "win.presentation",           "F5", "<Shift>F5", NULL,
1042
1043
          "win.continuous",             "c", NULL,
          "win.dual-page",              "d", NULL,
1044
1045
1046
1047
          "win.rotate-left",            "<Ctrl>Left", NULL,
          "win.rotate-right",           "<Ctrl>Right", NULL,
          "win.inverted-colors",        "<Ctrl>I", NULL,
          "win.reload",                 "<Ctrl>R", NULL,
1048
          "win.add-annotation",         "s", NULL,
1049
          "win.highlight-annotation",   "<Ctrl>H", NULL,
1050
1051
1052
          NULL
        };

Christian Persch's avatar
Christian Persch committed
1053
        EvApplication *application = EV_APPLICATION (gapplication);
1054
        const gchar **it;
Christian Persch's avatar
Christian Persch committed
1055

1056
1057
	g_application_set_resource_base_path (gapplication, "/org/gnome/evince");

Christian Persch's avatar
Christian Persch committed
1058
1059
        G_APPLICATION_CLASS (ev_application_parent_class)->startup (gapplication);

1060
1061
1062
1063
        g_action_map_add_action_entries (G_ACTION_MAP (application),
                                         app_menu_actions, G_N_ELEMENTS (app_menu_actions),
                                         application);

1064
1065
        for (it = action_accels; it[0]; it += g_strv_length ((gchar **)it) + 1)
                gtk_application_set_accels_for_action (GTK_APPLICATION (application), it[0], &it[1]);
Christian Persch's avatar
Christian Persch committed
1066
1067
}

1068
1069
static void
ev_application_shutdown (GApplication *gapplication)
1070
{
1071
1072
        EvApplication *application = EV_APPLICATION (gapplication);

1073
	if (application->uri) {
1074
#ifdef ENABLE_DBUS
1075
1076
		ev_application_unregister_uri (application,
					       application->uri);
1077
#endif
1078
1079
1080
		g_free (application->uri);
		application->uri = NULL;
	}
1081

1082
1083
	ev_application_accel_map_save (application);

1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
        g_free (application->dot_dir);
        application->dot_dir = NULL;

        G_APPLICATION_CLASS (ev_application_parent_class)->shutdown (gapplication);
}

static void
ev_application_activate (GApplication *gapplication)
{
        EvApplication *application = EV_APPLICATION (gapplication);
        GList *windows, *l;

        windows = gtk_application_get_windows (GTK_APPLICATION (application));
        for (l = windows; l != NULL; l = l->next) {
                if (!EV_IS_WINDOW (l->data))
                        continue;

                gtk_window_present (GTK_WINDOW (l->data));
        }
}

1105
#ifdef ENABLE_DBUS
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
static gboolean
ev_application_dbus_register (GApplication    *gapplication,
                              GDBusConnection *connection,
                              const gchar     *object_path,
                              GError         **error)
{
        EvApplication *application = EV_APPLICATION (gapplication);
        EvEvinceApplication *skeleton;

        if (!G_APPLICATION_CLASS (ev_application_parent_class)->dbus_register (gapplication,
                                                                               connection,
                                                                               object_path,
                                                                               error))
                return FALSE;

        skeleton = ev_evince_application_skeleton_new ();
        if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
                                               connection,
                                               APPLICATION_DBUS_OBJECT_PATH,
                                               error)) {
                g_object_unref (skeleton);

                return FALSE;
        }

        application->skeleton = skeleton;
        g_signal_connect (skeleton, "handle-get-window-list",
                          G_CALLBACK (handle_get_window_list_cb),
                          application);
        g_signal_connect (skeleton, "handle-reload",
                          G_CALLBACK (handle_reload_cb),
                          application);
        application->keys = ev_media_player_keys_new ();

        return TRUE;
}

static void
ev_application_dbus_unregister (GApplication    *gapplication,
                                GDBusConnection *connection,
                                const gchar     *object_path)
{
        EvApplication *application = EV_APPLICATION (gapplication);

        if (application->keys) {
                g_object_unref (application->keys);
                application->keys = NULL;
        }
1154
1155
1156
1157
        if (application->skeleton != NULL) {
                g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (application->skeleton));
                g_object_unref (application->skeleton);
                application->skeleton = NULL;
1158
        }
1159

1160
1161
1162
        G_APPLICATION_CLASS (ev_application_parent_class)->dbus_unregister (gapplication,
                                                                            connection,
                                                                            object_path);
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
1163
1164
}

1165
1166
#endif /* ENABLE_DBUS */

Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
1167
1168
1169
static void
ev_application_class_init (EvApplicationClass *ev_application_class)
{
1170
1171
        GApplicationClass *g_application_class = G_APPLICATION_CLASS (ev_application_class);

Christian Persch's avatar
Christian Persch committed
1172
        g_application_class->startup = ev_application_startup;
1173
1174
1175
1176
1177
1178
1179
        g_application_class->activate = ev_application_activate;
        g_application_class->shutdown = ev_application_shutdown;

#ifdef ENABLE_DBUS
        g_application_class->dbus_register = ev_application_dbus_register;
        g_application_class->dbus_unregister = ev_application_dbus_unregister;
#endif
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
1180
1181
1182
1183
1184
}

static void
ev_application_init (EvApplication *ev_application)
{
1185
1186
1187
1188
        ev_application->dot_dir = g_build_filename (g_get_user_config_dir (),
                                                    "evince", NULL);
        if (!g_file_test (ev_application->dot_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
                ev_application_migrate_config_dir (ev_application);
1189

1190
	ev_application_accel_map_load (ev_application);
1191
}
1192

1193
1194
gboolean
ev_application_has_window (EvApplication *application)
1195
{
1196
	GList *l, *windows;
1197

1198
1199
1200
1201
	windows = gtk_application_get_windows (GTK_APPLICATION (application));
	for (l = windows; l != NULL; l = l->next) {
		if (!EV_IS_WINDOW (l->data))
                        continue;
1202

1203
                return TRUE;
1204
1205
	}

1206
	return FALSE;
1207
}
1208