zathura.c 44.5 KB
Newer Older
1
2
/* See LICENSE file for license and copyright information */

3
#define _DEFAULT_SOURCE
Sebastian Ramacher's avatar
Sebastian Ramacher committed
4
#define _XOPEN_SOURCE 700
5

6
#include <errno.h>
Moritz Lipp's avatar
Moritz Lipp committed
7
#include <stdlib.h>
8
#include <math.h>
9
#include <string.h>
Moritz Lipp's avatar
Moritz Lipp committed
10

11
12
13
14
15
#include <girara/datastructures.h>
#include <girara/utils.h>
#include <girara/session.h>
#include <girara/statusbar.h>
#include <girara/settings.h>
16
#include <girara/shortcuts.h>
Sebastian Ramacher's avatar
Sebastian Ramacher committed
17
#include <girara/template.h>
18
#include <glib/gstdio.h>
19
#include <glib/gi18n.h>
20

Moritz Lipp's avatar
Moritz Lipp committed
21
22
#ifdef G_OS_UNIX
#include <glib-unix.h>
23
#include <gio/gunixinputstream.h>
Moritz Lipp's avatar
Moritz Lipp committed
24
25
#endif

26
#include "bookmarks.h"
Moritz Lipp's avatar
Moritz Lipp committed
27
28
#include "callbacks.h"
#include "config.h"
29
30
31
32
#ifdef WITH_SQLITE
#include "database-sqlite.h"
#endif
#include "database-plain.h"
33
#include "document.h"
Moritz Lipp's avatar
Moritz Lipp committed
34
#include "shortcuts.h"
Moritz Lipp's avatar
Moritz Lipp committed
35
#include "zathura.h"
Moritz Lipp's avatar
Moritz Lipp committed
36
#include "utils.h"
Moritz Lipp's avatar
Moritz Lipp committed
37
#include "marks.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
38
#include "render.h"
Moritz Lipp's avatar
Moritz Lipp committed
39
#include "page.h"
40
#include "page-widget.h"
41
#include "plugin.h"
42
#include "adjustment.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
43
#include "dbus-interface.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
44
#include "css-definitions.h"
45
#include "synctex.h"
46
#include "content-type.h"
Moritz Lipp's avatar
Moritz Lipp committed
47

Moritz Lipp's avatar
Moritz Lipp committed
48
typedef struct zathura_document_info_s {
Moritz Lipp's avatar
Update    
Moritz Lipp committed
49
  zathura_t* zathura;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
50
51
  char* path;
  char* password;
52
  int page_number;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
53
54
  char* mode;
  char* synctex;
Moritz Lipp's avatar
Update    
Moritz Lipp committed
55
56
} zathura_document_info_t;

57

58
static gboolean document_info_open(gpointer data);
Moritz Lipp's avatar
Moritz Lipp committed
59

Moritz Lipp's avatar
Moritz Lipp committed
60
61
62
63
#ifdef G_OS_UNIX
static gboolean zathura_signal_sigterm(gpointer data);
#endif

Sebastian Ramacher's avatar
Sebastian Ramacher committed
64
65
66
67
68
69
70
71
72
73
74
75
76
77
static void
free_document_info(zathura_document_info_t* document_info)
{
  if (document_info == NULL) {
    return;
  }

  g_free(document_info->path);
  g_free(document_info->password);
  g_free(document_info->mode);
  g_free(document_info->synctex);
  g_free(document_info);
}

Moritz Lipp's avatar
Moritz Lipp committed
78
/* function implementation */
Moritz Lipp's avatar
Moritz Lipp committed
79
zathura_t*
80
zathura_create(void)
81
{
82
83
84
85
  zathura_t* zathura = g_try_malloc0(sizeof(zathura_t));
  if (zathura == NULL) {
    return NULL;
  }
86

87
  /* global settings */
88
  zathura->global.search_direction = FORWARD;
89

90
  /* plugins */
Moritz Lipp's avatar
Moritz Lipp committed
91
92
  zathura->plugins.manager = zathura_plugin_manager_new();
  if (zathura->plugins.manager == NULL) {
93
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
94
  }
95

96
  /* UI */
97
98
  zathura->ui.session = girara_session_create();
  if (zathura->ui.session == NULL) {
99
    goto error_out;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
100
101
  }

Moritz Lipp's avatar
Moritz Lipp committed
102
103
104
105
106
#ifdef G_OS_UNIX
  /* signal handler */
  zathura->signals.sigterm = g_unix_signal_add(SIGTERM, zathura_signal_sigterm, zathura);
#endif

107
108
109
  /* MIME type detection */
  zathura->content_type_context = zathura_content_type_new();

110
111
112
113
114
115
116
117
118
119
  zathura->ui.session->global.data = zathura;

  return zathura;

error_out:

  zathura_free(zathura);

  return NULL;
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
120

121
122
static void
create_directories(zathura_t* zathura)
123
{
124
  static const unsigned int mode = 0711;
125

126
127
128
  if (g_mkdir_with_parents(zathura->config.config_dir, mode) == -1) {
    girara_error("Could not create '%s': %s", zathura->config.config_dir,
                 strerror(errno));
129
130
  }

131
132
133
  if (g_mkdir_with_parents(zathura->config.data_dir, mode) == -1) {
    girara_error("Could not create '%s': %s", zathura->config.data_dir,
                 strerror(errno));
134
  }
135
}
136

137
138
139
static bool
init_ui(zathura_t* zathura)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
140
  if (girara_session_init(zathura->ui.session, "zathura") == false) {
141
    return false;
142
143
144
  }

  /* girara events */
Moritz Lipp's avatar
Moritz Lipp committed
145
146
  zathura->ui.session->events.buffer_changed  = cb_buffer_changed;
  zathura->ui.session->events.unknown_command = cb_unknown_command;
147

148
  /* zathura signals */
149
150
151
  zathura->signals.refresh_view = g_signal_new(
    "refresh-view", GTK_TYPE_WIDGET, G_SIGNAL_RUN_LAST, 0, NULL, NULL,
    g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_POINTER);
152
153
154
155

  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.view), "refresh-view",
                   G_CALLBACK(cb_refresh_view), zathura);

156
  /* page view */
Moritz Lipp's avatar
Moritz Lipp committed
157
  zathura->ui.page_widget = gtk_grid_new();
158
159
  gtk_grid_set_row_homogeneous(GTK_GRID(zathura->ui.page_widget), TRUE);
  gtk_grid_set_column_homogeneous(GTK_GRID(zathura->ui.page_widget), TRUE);
Moritz Lipp's avatar
Moritz Lipp committed
160
  if (zathura->ui.page_widget == NULL) {
161
    return false;
162
163
  }

164
165
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "size-allocate",
                   G_CALLBACK(cb_view_resized), zathura);
166

167
  GtkAdjustment* hadjustment = gtk_scrolled_window_get_hadjustment(
168
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
169
170
171

  /* Connect hadjustment signals */
  g_signal_connect(G_OBJECT(hadjustment), "value-changed",
172
                   G_CALLBACK(cb_view_hadjustment_value_changed), zathura);
173
  g_signal_connect(G_OBJECT(hadjustment), "changed",
174
                   G_CALLBACK(cb_view_hadjustment_changed), zathura);
175
176

  GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(
177
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
178
179
180

  /* Connect vadjustment signals */
  g_signal_connect(G_OBJECT(vadjustment), "value-changed",
181
                   G_CALLBACK(cb_view_vadjustment_value_changed), zathura);
182
  g_signal_connect(G_OBJECT(vadjustment), "changed",
183
                   G_CALLBACK(cb_view_vadjustment_changed), zathura);
Moritz Lipp's avatar
Moritz Lipp committed
184

185
  /* page view alignment */
186
187
  gtk_widget_set_halign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
  gtk_widget_set_valign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
188

189
190
191
192
  gtk_widget_set_hexpand_set(zathura->ui.page_widget, TRUE);
  gtk_widget_set_hexpand(zathura->ui.page_widget, FALSE);
  gtk_widget_set_vexpand_set(zathura->ui.page_widget, TRUE);
  gtk_widget_set_vexpand(zathura->ui.page_widget, FALSE);
193

194
  gtk_widget_show(zathura->ui.page_widget);
Moritz Lipp's avatar
Moritz Lipp committed
195

196
  /* statusbar */
197
198
  zathura->ui.statusbar.file =
    girara_statusbar_item_add(zathura->ui.session, TRUE, TRUE, TRUE, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
199
  if (zathura->ui.statusbar.file == NULL) {
200
    return false;
Moritz Lipp's avatar
Moritz Lipp committed
201
202
  }

203
204
  zathura->ui.statusbar.buffer =
    girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
205
  if (zathura->ui.statusbar.buffer == NULL) {
206
    return false;
Moritz Lipp's avatar
Moritz Lipp committed
207
208
  }

209
210
  zathura->ui.statusbar.page_number =
    girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
211
  if (zathura->ui.statusbar.page_number == NULL) {
212
    return false;
Moritz Lipp's avatar
Moritz Lipp committed
213
214
  }

215
216
  girara_statusbar_item_set_text(zathura->ui.session,
                                 zathura->ui.statusbar.file, _("[No name]"));
Moritz Lipp's avatar
Moritz Lipp committed
217

Moritz Lipp's avatar
Moritz Lipp committed
218
  /* signals */
219
220
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "destroy",
                   G_CALLBACK(cb_destroy), zathura);
221

222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
  return true;
}

static void
init_css(zathura_t* zathura)
{
  GiraraTemplate* csstemplate =
    girara_session_get_template(zathura->ui.session);

  static const char* index_settings[] = {
    "index-fg",
    "index-bg",
    "index-active-fg",
    "index-active-bg"
  };

  for (size_t s = 0; s < LENGTH(index_settings); ++s) {
    girara_template_add_variable(csstemplate, index_settings[s]);

    char*   tmp_value = NULL;
    GdkRGBA rgba      = {0, 0, 0, 0};
    girara_setting_get(zathura->ui.session, index_settings[s], &tmp_value);
    if (tmp_value != NULL) {
      gdk_rgba_parse(&rgba, tmp_value);
      g_free(tmp_value);
    }

    char* color = gdk_rgba_to_string(&rgba);
    girara_template_set_variable_value(csstemplate, index_settings[s], color);
    g_free(color);
  }

  char* css = g_strdup_printf("%s\n%s", girara_template_get_base(csstemplate),
                              CSS_TEMPLATE_INDEX);
  girara_template_set_base(csstemplate, css);
  g_free(css);
}

static void
init_database(zathura_t* zathura)
{
263
264
265
266
  char* database = NULL;
  girara_setting_get(zathura->ui.session, "database", &database);

  if (g_strcmp0(database, "plain") == 0) {
267
    girara_debug("Using plain database backend.");
268
269
270
    zathura->database = zathura_plaindatabase_new(zathura->config.data_dir);
#ifdef WITH_SQLITE
  } else if (g_strcmp0(database, "sqlite") == 0) {
271
    girara_debug("Using sqlite database backend.");
272
273
    char* tmp =
      g_build_filename(zathura->config.data_dir, "bookmarks.sqlite", NULL);
274
275
276
    zathura->database = zathura_sqldatabase_new(tmp);
    g_free(tmp);
#endif
Sebastian Ramacher's avatar
Sebastian Ramacher committed
277
  } else if (g_strcmp0(database, "null") != 0) {
278
279
280
    girara_error("Database backend '%s' is not supported.", database);
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
281
  if (zathura->database == NULL && g_strcmp0(database, "null") != 0) {
282
283
284
285
286
287
    girara_error(
      "Unable to initialize database. Bookmarks won't be available.");
  }
  else {
    g_object_set(G_OBJECT(zathura->ui.session->command_history), "io",
                 zathura->database, NULL);
288
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
289
  g_free(database);
290
}
291

292
293
294
static void
init_jumplist(zathura_t* zathura)
{
295
296
  int jumplist_size = 20;
  girara_setting_get(zathura->ui.session, "jumplist-size", &jumplist_size);
297

298
  zathura->jumplist.max_size = jumplist_size < 0 ? 0 : jumplist_size;
299
300
301
302
  zathura->jumplist.list     = NULL;
  zathura->jumplist.size     = 0;
  zathura->jumplist.cur      = NULL;
}
303

304
305
306
307
308
static void
init_shortcut_helpers(zathura_t* zathura)
{
  zathura->shortcut.mouse.x = 0;
  zathura->shortcut.mouse.y = 0;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
309

310
  zathura->shortcut.toggle_page_mode.pages = 2;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
311

312
313
314
315
  zathura->shortcut.toggle_presentation_mode.pages                  = 1;
  zathura->shortcut.toggle_presentation_mode.first_page_column_list = NULL;
  zathura->shortcut.toggle_presentation_mode.zoom                   = 1.0;
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
316

317
318
319
320
321
322
bool
zathura_init(zathura_t* zathura)
{
  if (zathura == NULL) {
    return false;
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
323

324
325
326
327
328
329
330
331
332
333
334
335
336
  /* create zathura (config/data) directory */
  create_directories(zathura);

  /* load plugins */
  zathura_plugin_manager_load(zathura->plugins.manager);

  /* configuration */
  config_load_default(zathura);
  config_load_files(zathura);

  /* UI */
  if (!init_ui(zathura)) {
    goto error_free;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
337
338
  }

339
340
  /* database */
  init_database(zathura);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
341

342
343
344
345
  /* bookmarks */
  zathura->bookmarks.bookmarks = girara_sorted_list_new2(
    (girara_compare_function_t)zathura_bookmarks_compare,
    (girara_free_function_t)zathura_bookmark_free);
346

347
348
  /* jumplist */
  init_jumplist(zathura);
349

350
351
352
353
354
  /* CSS for index mode */
  init_css(zathura);

  /* Shortcut helpers */
  init_shortcut_helpers(zathura);
355

356
  /* Start D-Bus service */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
357
358
359
360
  bool dbus = true;
  girara_setting_get(zathura->ui.session, "dbus-service", &dbus);
  if (dbus == true) {
    zathura->dbus = zathura_dbus_new(zathura);
361
362
  }

363
  return true;
Moritz Lipp's avatar
Moritz Lipp committed
364
365
366

error_free:

Moritz Lipp's avatar
Moritz Lipp committed
367
  if (zathura->ui.page_widget != NULL) {
368
    g_object_unref(zathura->ui.page_widget);
369
370
  }

371
  return false;
Moritz Lipp's avatar
Moritz Lipp committed
372
373
374
375
376
377
378
379
380
}

void
zathura_free(zathura_t* zathura)
{
  if (zathura == NULL) {
    return;
  }

Moritz Lipp's avatar
Moritz Lipp committed
381
  document_close(zathura, false);
382

383
384
385
  /* MIME type detection */
  zathura_content_type_free(zathura->content_type_context);

386
387
388
389
390
391
392
#ifdef G_OS_UNIX
  if (zathura->signals.sigterm > 0) {
    g_source_remove(zathura->signals.sigterm);
    zathura->signals.sigterm = 0;
  }
#endif

393
  /* stop D-Bus */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
394
395
396
  if (zathura->dbus != NULL) {
    g_object_unref(zathura->dbus);
    zathura->dbus = NULL;
397
398
  }

Moritz Lipp's avatar
Moritz Lipp committed
399
400
401
402
  if (zathura->ui.session != NULL) {
    girara_session_destroy(zathura->ui.session);
  }

403
  /* shortcut */
404
405
406
  if (zathura->shortcut.toggle_presentation_mode.first_page_column_list != NULL) {
    g_free(zathura->shortcut.toggle_presentation_mode.first_page_column_list);
  }
407

408
409
410
411
412
413
  /* stdin support */
  if (zathura->stdin_support.file != NULL) {
    g_unlink(zathura->stdin_support.file);
    g_free(zathura->stdin_support.file);
  }

414
415
416
  /* bookmarks */
  girara_list_free(zathura->bookmarks.bookmarks);

417
  /* database */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
418
419
420
  if (zathura->database != NULL) {
    g_object_unref(G_OBJECT(zathura->database));
  }
421

422
  /* free print settings */
Moritz Lipp's avatar
Moritz Lipp committed
423
  if (zathura->print.settings != NULL) {
424
425
426
427
428
429
    g_object_unref(zathura->print.settings);
  }

  if (zathura->print.page_setup != NULL) {
    g_object_unref(zathura->print.page_setup);
  }
430

Moritz Lipp's avatar
Moritz Lipp committed
431
  /* free registered plugins */
Moritz Lipp's avatar
Moritz Lipp committed
432
  zathura_plugin_manager_free(zathura->plugins.manager);
433
434
435
436

  /* free config variables */
  g_free(zathura->config.config_dir);
  g_free(zathura->config.data_dir);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
437
  g_free(zathura->config.cache_dir);
Moritz Lipp's avatar
Moritz Lipp committed
438

439
440
441
442
443
444
445
446
447
  /* free jumplist */
  if (zathura->jumplist.list != NULL) {
    girara_list_free(zathura->jumplist.list);
  }

  if (zathura->jumplist.cur != NULL) {
    girara_list_iterator_free(zathura->jumplist.cur);
  }

448
  g_free(zathura);
449
450
}

451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
void
zathura_set_xid(zathura_t* zathura, Window xid)
{
  g_return_if_fail(zathura != NULL);

  zathura->ui.session->gtk.embed = xid;
}

void
zathura_set_config_dir(zathura_t* zathura, const char* dir)
{
  g_return_if_fail(zathura != NULL);

  if (dir != NULL) {
    zathura->config.config_dir = g_strdup(dir);
  } else {
    gchar* path = girara_get_xdg_path(XDG_CONFIG);
    zathura->config.config_dir = g_build_filename(path, "zathura", NULL);
    g_free(path);
  }
}

void
zathura_set_data_dir(zathura_t* zathura, const char* dir)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
476
477
  g_return_if_fail(zathura != NULL);

478
479
480
481
482
483
484
  if (dir != NULL) {
    zathura->config.data_dir = g_strdup(dir);
  } else {
    gchar* path = girara_get_xdg_path(XDG_DATA);
    zathura->config.data_dir = g_build_filename(path, "zathura", NULL);
    g_free(path);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
485
}
486

Sebastian Ramacher's avatar
Sebastian Ramacher committed
487
488
489
void
zathura_set_cache_dir(zathura_t* zathura, const char* dir)
{
490
  g_return_if_fail(zathura != NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
491
492
493
494
495
496
497
498

  if (dir != NULL) {
    zathura->config.cache_dir = g_strdup(dir);
  } else {
    gchar* path = girara_get_xdg_path(XDG_CACHE);
    zathura->config.cache_dir = g_build_filename(path, "zathura", NULL);
    g_free(path);
  }
499
500
501
502
503
504
505
506
507
508
509
}

void
zathura_set_plugin_dir(zathura_t* zathura, const char* dir)
{
  g_return_if_fail(zathura != NULL);
  g_return_if_fail(zathura->plugins.manager != NULL);

  if (dir != NULL) {
    girara_list_t* paths = girara_split_path_array(dir);
    GIRARA_LIST_FOREACH(paths, char*, iter, path)
Moritz Lipp's avatar
Moritz Lipp committed
510
    zathura_plugin_manager_add_dir(zathura->plugins.manager, path);
511
512
513
514
515
516
    GIRARA_LIST_FOREACH_END(paths, char*, iter, path);
    girara_list_free(paths);
  } else {
#ifdef ZATHURA_PLUGINDIR
    girara_list_t* paths = girara_split_path_array(ZATHURA_PLUGINDIR);
    GIRARA_LIST_FOREACH(paths, char*, iter, path)
Moritz Lipp's avatar
Moritz Lipp committed
517
    zathura_plugin_manager_add_dir(zathura->plugins.manager, path);
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
    GIRARA_LIST_FOREACH_END(paths, char*, iter, path);
    girara_list_free(paths);
#endif
  }

}

void
zathura_set_argv(zathura_t* zathura, char** argv)
{
  g_return_if_fail(zathura != NULL);

  zathura->global.arguments = argv;
}

533
#ifdef G_OS_UNIX
534
static gchar*
Sebastian Ramacher's avatar
Sebastian Ramacher committed
535
prepare_document_open_from_stdin(const char* path)
536
{
537
538
539
540
541
542
543
544
545
546
547
548
549
550
  int infileno = -1;
  if (g_strcmp0(path, "-") == 0) {
    infileno = fileno(stdin);
  } else if (g_str_has_prefix(path, "/proc/self/fd/") == true) {
    char* begin = g_strrstr(path, "/") + 1;
    gint64 temp = g_ascii_strtoll(begin, NULL, 0);
    if (temp > INT_MAX || temp < 0) {
      return NULL;
    }
    infileno = (int) temp;
  } else {
    return NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
551
552
553
554
555
  if (infileno == -1) {
    girara_error("Can not read from file descriptor.");
    return NULL;
  }

556
557
558
559
560
561
562
563
564
565
566
  GInputStream* input_stream = g_unix_input_stream_new(infileno, false);
  if (input_stream == NULL) {
    girara_error("Can not read from file descriptor.");
    return NULL;

  }

  GFileIOStream* iostream = NULL;
  GError*        error    = NULL;
  GFile* tmpfile = g_file_new_tmp("zathura.stdin.XXXXXX", &iostream, &error);
  if (tmpfile == NULL) {
567
568
569
570
    if (error != NULL) {
      girara_error("Can not create temporary file: %s", error->message);
      g_error_free(error);
    }
571
    g_object_unref(input_stream);
572
573
574
    return NULL;
  }

575
576
577
578
579
580
581
582
583
  const ssize_t count = g_output_stream_splice(
    g_io_stream_get_output_stream(G_IO_STREAM(iostream)), input_stream,
    G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);
  g_object_unref(input_stream);
  g_object_unref(iostream);
  if (count == -1) {
    if (error != NULL) {
      girara_error("Can not write to temporary file: %s", error->message);
      g_error_free(error);
584
    }
585
586
    g_file_delete(tmpfile, NULL, NULL);
    g_object_unref(tmpfile);
587
588
589
    return NULL;
  }

590
591
592
  char* file = g_file_get_path(tmpfile);
  g_object_unref(tmpfile);

593
594
  return file;
}
595
#endif
596

Lukas K.'s avatar
Lukas K. committed
597
static gchar*
Sebastian Ramacher's avatar
Sebastian Ramacher committed
598
prepare_document_open_from_gfile(GFile* source)
Lukas K.'s avatar
Lukas K. committed
599
{
600
  gchar*         file     = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
601
  GFileIOStream* iostream = NULL;
602
  GError*        error    = NULL;
Lukas K.'s avatar
Lukas K. committed
603

604
  GFile* tmpfile = g_file_new_tmp("zathura.gio.XXXXXX", &iostream, &error);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
605
  if (tmpfile == NULL) {
Lukas K.'s avatar
Lukas K. committed
606
607
608
609
610
611
612
    if (error != NULL) {
      girara_error("Can not create temporary file: %s", error->message);
      g_error_free(error);
    }
    return NULL;
  }

613
614
  gboolean rc = g_file_copy(source, tmpfile, G_FILE_COPY_OVERWRITE, NULL, NULL,
                            NULL, &error);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
615
  if (rc == FALSE) {
Lukas K.'s avatar
Lukas K. committed
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
    if (error != NULL) {
      girara_error("Can not copy to temporary file: %s", error->message);
      g_error_free(error);
    }
    g_object_unref(iostream);
    g_object_unref(tmpfile);
    return NULL;
  }

  file = g_file_get_path(tmpfile);
  g_object_unref(iostream);
  g_object_unref(tmpfile);

  return file;
}

632
static gboolean
Moritz Lipp's avatar
Update    
Moritz Lipp committed
633
634
635
636
document_info_open(gpointer data)
{
  zathura_document_info_t* document_info = data;
  g_return_val_if_fail(document_info != NULL, FALSE);
Lukas K.'s avatar
Lukas K. committed
637
  char* uri = NULL;
Moritz Lipp's avatar
Update    
Moritz Lipp committed
638

639
  if (document_info->zathura != NULL && document_info->path != NULL) {
640
    char* file = NULL;
641
642
    if (g_strcmp0(document_info->path, "-") == 0 ||
        g_str_has_prefix(document_info->path, "/proc/self/fd/") == true) {
643
#ifdef G_OS_UNIX
Sebastian Ramacher's avatar
Sebastian Ramacher committed
644
      file = prepare_document_open_from_stdin(document_info->path);
645
#endif
646
647
      if (file == NULL) {
        girara_notify(document_info->zathura->ui.session, GIRARA_ERROR,
648
                      _("Could not read file from stdin and write it to a temporary file."));
649
650
      } else {
        document_info->zathura->stdin_support.file = g_strdup(file);
651
652
      }
    } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
653
654
655
656
      GFile* gf = g_file_new_for_commandline_arg(document_info->path);
      if (g_file_is_native(gf) == TRUE) {
        /* file was given as a native path */
        file = g_file_get_path(gf);
Lukas K.'s avatar
Lukas K. committed
657
658
      }
      else {
659
        /* copy file with GIO */
Lukas K.'s avatar
Lukas K. committed
660
        uri = g_file_get_uri(gf);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
661
        file = prepare_document_open_from_gfile(gf);
662
663
664
        if (file == NULL) {
          girara_notify(document_info->zathura->ui.session, GIRARA_ERROR,
                        _("Could not read file from GIO and copy it to a temporary file."));
Lukas K.'s avatar
Lukas K. committed
665
        } else {
666
          document_info->zathura->stdin_support.file = g_strdup(file);
Lukas K.'s avatar
Lukas K. committed
667
668
669
        }
      }
      g_object_unref(gf);
670
671
672
    }

    if (file != NULL) {
673
      if (document_info->synctex != NULL) {
Lukas K.'s avatar
Lukas K. committed
674
        document_open_synctex(document_info->zathura, file, uri, 
675
676
                              document_info->password, document_info->synctex);
      } else {
Lukas K.'s avatar
Lukas K. committed
677
        document_open(document_info->zathura, file, uri, document_info->password,
678
679
                      document_info->page_number);
      }
680
      g_free(file);
Lukas K.'s avatar
Lukas K. committed
681
      g_free(uri);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
682
683
684
685
686
687
688
689
690
691
692
693

      if (document_info->mode != NULL) {
        if (g_strcmp0(document_info->mode, "presentation") == 0) {
          sc_toggle_presentation(document_info->zathura->ui.session, NULL, NULL,
                                 0);
        } else if (g_strcmp0(document_info->mode, "fullscreen") == 0) {
          sc_toggle_fullscreen(document_info->zathura->ui.session, NULL, NULL,
                               0);
        } else {
          girara_error("Unknown mode: %s", document_info->mode);
        }
      }
694
    }
Moritz Lipp's avatar
Update    
Moritz Lipp committed
695
696
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
697
  free_document_info(document_info);
Moritz Lipp's avatar
Update    
Moritz Lipp committed
698
  return FALSE;
699
700
}

Lukas K.'s avatar
Lukas K. committed
701
702
char*
get_formatted_filename(zathura_t* zathura, bool statusbar)
703
704
{
  bool basename_only = false;
Lukas K.'s avatar
Lukas K. committed
705
  const char* file_path = zathura_document_get_uri(zathura->document);
706
  if (file_path == NULL) {
Lukas K.'s avatar
Lukas K. committed
707
708
    file_path = zathura_document_get_path(zathura->document);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
709
  if (statusbar == true) {
710
    girara_setting_get(zathura->ui.session, "statusbar-basename", &basename_only);
Lukas K.'s avatar
Lukas K. committed
711
712
  } else {
    girara_setting_get(zathura->ui.session, "window-title-basename", &basename_only);
713
714
  }

715
716
  if (basename_only == false) {
    bool home_tilde = false;
717
718
719
720
721
722
    if (statusbar) {
      girara_setting_get(zathura->ui.session, "statusbar-home-tilde", &home_tilde);
    } else {
      girara_setting_get(zathura->ui.session, "window-title-home-tilde", &home_tilde);
    }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
723
    const size_t file_path_len = file_path ? strlen(file_path) : 0;
724

Sebastian Ramacher's avatar
Sebastian Ramacher committed
725
726
727
    if (home_tilde == true) {
      char* home = girara_get_home_directory(NULL);
      const size_t home_len = home ? strlen(home) : 0;
728

Sidharth Kapur's avatar
Sidharth Kapur committed
729
730
731
732
      if (home_len > 1
          && file_path_len >= home_len
          && g_str_has_prefix(file_path, home)
          && (!file_path[home_len] || file_path[home_len] == '/')) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
733
        g_free(home);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
734
        return g_strdup_printf("~%s", &file_path[home_len]);
735
      } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
736
        g_free(home);
737
        return g_strdup(file_path);
738
739
      }
    } else {
740
      return g_strdup(file_path);
741
742
    }
  } else {
743
    const char* basename = zathura_document_get_basename(zathura->document);
744
    return g_strdup(basename);
745
746
747
  }
}

748
749
750
751
752
753
754
755
756
757
static gboolean
document_open_password_dialog(gpointer data)
{
  zathura_password_dialog_info_t* password_dialog_info = data;

  girara_dialog(password_dialog_info->zathura->ui.session, _("Enter password:"), true, NULL,
                (girara_callback_inputbar_activate_t) cb_password_dialog, password_dialog_info);
  return FALSE;
}

Moritz Lipp's avatar
Moritz Lipp committed
758
bool
Lukas K.'s avatar
Lukas K. committed
759
document_open(zathura_t* zathura, const char* path, const char* uri, const char* password,
760
              int page_number)
Moritz Lipp's avatar
Moritz Lipp committed
761
{
762
  if (zathura == NULL || zathura->plugins.manager == NULL || path == NULL) {
763
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
764
765
  }

766
  gchar* file_uri = NULL;
767
  zathura_error_t error = ZATHURA_ERROR_OK;
768
  zathura_document_t* document = zathura_document_open(zathura, path, uri, password, &error);
Moritz Lipp's avatar
Moritz Lipp committed
769

Moritz Lipp's avatar
Moritz Lipp committed
770
  if (document == NULL) {
771
    if (error == ZATHURA_ERROR_INVALID_PASSWORD) {
772
      girara_debug("Invalid or no password.");
773
774
775
      zathura_password_dialog_info_t* password_dialog_info = malloc(sizeof(zathura_password_dialog_info_t));
      if (password_dialog_info != NULL) {
        password_dialog_info->zathura = zathura;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
776
        password_dialog_info->path = g_strdup(path);
Lukas K.'s avatar
Lukas K. committed
777
        password_dialog_info->uri = g_strdup(uri);
778

Sebastian Ramacher's avatar
Sebastian Ramacher committed
779
        if (password_dialog_info->path != NULL) {
780
          gdk_threads_add_idle(document_open_password_dialog, password_dialog_info);
781
782
783
784
785
786
787
          goto error_out;
        } else {
          free(password_dialog_info);
        }
      }
      goto error_out;
    }
Moritz Lipp's avatar
Moritz Lipp committed
788
789
790
    if (error == ZATHURA_ERROR_OK ) {
      girara_notify(zathura->ui.session, GIRARA_ERROR, _("Unsupported file type. Please install the necessary plugin."));
    }
791
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
792
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
793

Lukas K.'s avatar
Lukas K. committed
794
  zathura->document = document;
Moritz Lipp's avatar
Moritz Lipp committed
795

796
797
  const char* file_path        = zathura_document_get_path(document);
  unsigned int number_of_pages = zathura_document_get_number_of_pages(document);
798

799
800
801
802
803
804
  if (number_of_pages == 0) {
    girara_notify(zathura->ui.session, GIRARA_WARNING,
        _("Document does not contain any pages"));
    goto error_free;
  }

805
  /* read history file */
806
807
808
809
810
811
  zathura_fileinfo_t file_info = {
    .current_page = 0,
    .page_offset = 0,
    .scale = 1,
    .rotation = 0,
    .pages_per_row = 0,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
812
    .first_page_column_list = NULL,
813
814
815
    .position_x = 0,
    .position_y = 0
  };
816
817
818
819
  bool known_file = false;
  if (zathura->database != NULL) {
    known_file = zathura_db_get_fileinfo(zathura->database, file_path, &file_info);
  }
820

Moritz Lipp's avatar
Moritz Lipp committed
821
  /* set page offset */
822
  zathura_document_set_page_offset(document, file_info.page_offset);
823

824
  /* check for valid scale value */
825
826
827
  if (file_info.scale <= DBL_EPSILON) {
    file_info.scale = 1;
  }
Moritz Lipp's avatar
Moritz Lipp committed
828
829
  zathura_document_set_scale(document,
      zathura_correct_scale_value(zathura->ui.session, file_info.scale));
830
831

  /* check current page number */
832
833
834
835
836
837
  /* if it wasn't specified on the command-line, get it from file_info */
  if (page_number == ZATHURA_PAGE_NUMBER_UNSPECIFIED)
    page_number = file_info.current_page;
  if (page_number < 0)
    page_number += number_of_pages;
  if ((unsigned)page_number > number_of_pages) {
838
839
    girara_warning("document info: '%s' has an invalid page number", file_path);
    zathura_document_set_current_page_number(document, 0);
840
  } else {
841
    zathura_document_set_current_page_number(document, page_number);
842
843
844
  }

  /* check for valid rotation */
845
  if (file_info.rotation % 90 != 0) {
846
847
848
    girara_warning("document info: '%s' has an invalid rotation", file_path);
    zathura_document_set_rotation(document, 0);
  } else {
849
    zathura_document_set_rotation(document, file_info.rotation % 360);
850
851
852
853
854
855
856
857
858
859
860
  }

  /* jump to first page if setting enabled */
  bool always_first_page = false;
  girara_setting_get(zathura->ui.session, "open-first-page", &always_first_page);
  if (always_first_page == true) {
    zathura_document_set_current_page_number(document, 0);
  }

  /* apply open adjustment */
  char* adjust_open = "best-fit";
861
  if (known_file == false && girara_setting_get(zathura->ui.session, "adjust-open", &(adjust_open)) == true) {
862
863
864
865
866
867
868
869
    if (g_strcmp0(adjust_open, "best-fit") == 0) {
      zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_BESTFIT);
    } else if (g_strcmp0(adjust_open, "width") == 0) {
      zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_WIDTH);
    } else {
      zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_NONE);
    }
    g_free(adjust_open);
870
871
  } else {
    zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_NONE);
872
873
  }

874
875
876
877
878
  /* initialize bisect state */
  zathura->bisect.start = 0;
  zathura->bisect.last_jump = zathura_document_get_current_page_number(document);
  zathura->bisect.end = number_of_pages - 1;

879
  /* update statusbar */
Lukas K.'s avatar
Lukas K. committed
880
  char* filename = get_formatted_filename(zathura, true);
881
882
  girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, filename);
  g_free(filename);
883

Moritz Lipp's avatar
Moritz Lipp committed
884
  /* install file monitor */
885
  file_uri = g_filename_to_uri(file_path, NULL, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
886
887
888
889
890
  if (file_uri == NULL) {
    goto error_free;
  }

  if (zathura->file_monitor.file == NULL) {
891
892
893
894
    zathura->file_monitor.file = g_file_new_for_uri(file_uri);
    if (zathura->file_monitor.file == NULL) {
      goto error_free;
    }
Moritz Lipp's avatar
Moritz Lipp committed
895
896
897
  }

  if (zathura->file_monitor.monitor == NULL) {
898
899
900
901
902
    zathura->file_monitor.monitor = g_file_monitor_file(zathura->file_monitor.file, G_FILE_MONITOR_NONE, NULL, NULL);
    if (zathura->file_monitor.monitor == NULL) {
      goto error_free;
    }
    g_signal_connect(G_OBJECT(zathura->file_monitor.monitor), "changed", G_CALLBACK(cb_file_monitor), zathura->ui.session);
Moritz Lipp's avatar
Moritz Lipp committed
903
904
905
  }

  if (zathura->file_monitor.file_path == NULL) {
906
    zathura->file_monitor.file_path = g_strdup(file_path);
907
908
909
    if (zathura->file_monitor.file_path == NULL) {
      goto error_free;
    }
Moritz Lipp's avatar
Moritz Lipp committed
910
911
  }

912
  if (password != NULL) {
913
    g_free(zathura->file_monitor.password);
914
    zathura->file_monitor.password = g_strdup(password);
Moritz Lipp's avatar
Moritz Lipp committed
915
916
917
918
919
    if (zathura->file_monitor.password == NULL) {
      goto error_free;
    }
  }

Moritz Lipp's avatar
Moritz Lipp committed
920
921
922
923
924
925
  /* create marks list */
  zathura->global.marks = girara_list_new2((girara_free_function_t) mark_free);
  if (zathura->global.marks == NULL) {
    goto error_free;
  }

926
927
928
929
930
931
932
933
934
  /* page cache size */
  int cache_size = 0;
  girara_setting_get(zathura->ui.session, "page-cache-size", &cache_size);
  if (cache_size <= 0) {
    girara_warning("page-cache-size is not positive, using %d instead",
        ZATHURA_PAGE_CACHE_DEFAULT_SIZE);
    cache_size = ZATHURA_PAGE_CACHE_DEFAULT_SIZE;
  }

935
  /* threads */
936
  zathura->sync.render_thread = zathura_renderer_new(cache_size);
937
938
939
940
941

  if (zathura->sync.render_thread == NULL) {
    goto error_free;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
942
  /* set up recolor info in ZathuraRenderer */
943
  char* recolor_dark  = NULL;
944
945
946
947
948
949
950
951
  char* recolor_light = NULL;
  girara_setting_get(zathura->ui.session, "recolor-darkcolor", &recolor_dark);
  girara_setting_get(zathura->ui.session, "recolor-lightcolor", &recolor_light);
  zathura_renderer_set_recolor_colors_str(zathura->sync.render_thread,
      recolor_light, recolor_dark);
  g_free(recolor_dark);
  g_free(recolor_light);

952
953
954
955
956
  bool recolor = false;
  girara_setting_get(zathura->ui.session, "recolor", &recolor);
  zathura_renderer_enable_recolor(zathura->sync.render_thread, recolor);
  girara_setting_get(zathura->ui.session, "recolor-keephue", &recolor);
  zathura_renderer_enable_recolor_hue(zathura->sync.render_thread, recolor);
957
958
  girara_setting_get(zathura->ui.session, "recolor-reverse-video", &recolor);
  zathura_renderer_enable_recolor_reverse_video(zathura->sync.render_thread, recolor);
959

960
961
962
963
964
965
  /* get view port size */
  GtkAdjustment* hadjustment = gtk_scrolled_window_get_hadjustment(
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
  GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));

966
  const unsigned int view_width = (unsigned int)floor(gtk_adjustment_get_page_size(hadjustment));
967
  zathura_document_set_viewport_width(zathura->document, view_width);
968
  const unsigned int view_height = (unsigned int)floor(gtk_adjustment_get_page_size(vadjustment));
969
970
  zathura_document_set_viewport_height(zathura->document, view_height);

971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
  /* create blank pages */
  zathura->pages = calloc(number_of_pages, sizeof(GtkWidget*));
  if (zathura->pages == NULL) {
    goto error_free;
  }

  for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
    zathura_page_t* page = zathura_document_get_page(document, page_id);
    if (page == NULL) {
      goto error_free;
    }

    GtkWidget* page_widget = zathura_page_widget_new(zathura, page);
    if (page_widget == NULL) {
      goto error_free;
    }

akbjker's avatar
akbjker committed
988
    g_object_ref(page_widget);
989
990
    zathura->pages[page_id] = page_widget;

991
992
993
    gtk_widget_set_halign(page_widget, GTK_ALIGN_CENTER);
    gtk_widget_set_valign(page_widget, GTK_ALIGN_CENTER);

994
995
996
    g_signal_connect(G_OBJECT(page_widget), "text-selected",
        G_CALLBACK(cb_page_widget_text_selected), zathura);
    g_signal_connect(G_OBJECT(page_widget), "image-selected",
997
        G_CALLBACK(cb_page_widget_image_selected), zathura);
998
999
1000
1001
    g_signal_connect(G_OBJECT(page_widget), "enter-link",
        G_CALLBACK(cb_page_widget_link), (gpointer) true);
    g_signal_connect(G_OBJECT(page_widget), "leave-link",
        G_CALLBACK(cb_page_widget_link), (gpointer) false);
1002
1003
    g_signal_connect(G_OBJECT(page_widget), "scaled-button-release",
        G_CALLBACK(cb_page_widget_scaled_button_release), zathura);
1004
1005
  }

1006
  /* view mode */
1007
  unsigned int pages_per_row = 1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
1008
  char* first_page_column_list = NULL;
1009
1010
1011
1012
  unsigned int page_padding = 1;

  girara_setting_get(zathura->ui.session, "page-padding", &page_padding);

1013
1014
1015
1016
1017
  if (file_info.pages_per_row > 0) {
    pages_per_row = file_info.pages_per_row;
  } else {
    girara_setting_get(zathura->ui.session, "pages-per-row", &pages_per_row);
  }
Moritz Lipp's avatar
Moritz Lipp committed
1018

1019
  /* read first_page_column list */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
1020
  if (file_info.first_page_column_list != NULL && strcmp(file_info.first_page_column_list, "")) {
Sebastian Neuser's avatar