zathura.c 44.7 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>
Pavel Borzenkov's avatar
Pavel Borzenkov committed
8
#include <unistd.h>
9
#include <math.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
23
24
#ifdef G_OS_UNIX
#include <glib-unix.h>
#endif

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

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

55

56
static gboolean document_info_open(gpointer data);
57
58
59
static void zathura_jumplist_reset_current(zathura_t* zathura);
static void zathura_jumplist_append_jump(zathura_t* zathura);
static void zathura_jumplist_save(zathura_t* zathura);
Moritz Lipp's avatar
Moritz Lipp committed
60

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

Moritz Lipp's avatar
Moritz Lipp committed
65
/* function implementation */
Moritz Lipp's avatar
Moritz Lipp committed
66
zathura_t*
67
zathura_create(void)
68
{
69
70
71
72
  zathura_t* zathura = g_try_malloc0(sizeof(zathura_t));
  if (zathura == NULL) {
    return NULL;
  }
73

74
  /* global settings */
75
  zathura->global.search_direction = FORWARD;
76

77
  /* plugins */
Moritz Lipp's avatar
Moritz Lipp committed
78
79
  zathura->plugins.manager = zathura_plugin_manager_new();
  if (zathura->plugins.manager == NULL) {
80
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
81
  }
82

83
84
85
  /* UI */
  if ((zathura->ui.session = girara_session_create()) == NULL) {
    goto error_out;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
86
87
  }

Moritz Lipp's avatar
Moritz Lipp committed
88
89
90
91
92
#ifdef G_OS_UNIX
  /* signal handler */
  zathura->signals.sigterm = g_unix_signal_add(SIGTERM, zathura_signal_sigterm, zathura);
#endif

93
94
95
96
97
98
99
100
101
102
  zathura->ui.session->global.data = zathura;

  return zathura;

error_out:

  zathura_free(zathura);

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

104
105
106
107
108
bool
zathura_init(zathura_t* zathura)
{
  if (zathura == NULL) {
    return false;
109
110
  }

111
  /* create zathura (config/data) directory */
112
113
114
115
116
117
118
  if (g_mkdir_with_parents(zathura->config.config_dir, 0771) == -1) {
    girara_error("Could not create '%s': %s", zathura->config.config_dir, strerror(errno));
  }

  if (g_mkdir_with_parents(zathura->config.data_dir, 0771) == -1) {
    girara_error("Could not create '%s': %s", zathura->config.data_dir, strerror(errno));
  }
119

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

123
124
  /* configuration */
  config_load_default(zathura);
125
  config_load_files(zathura);
126

127
  /* UI */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
128
  if (girara_session_init(zathura->ui.session, "zathura") == false) {
Moritz Lipp's avatar
Moritz Lipp committed
129
    goto error_free;
130
131
132
  }

  /* girara events */
Moritz Lipp's avatar
Moritz Lipp committed
133
134
  zathura->ui.session->events.buffer_changed  = cb_buffer_changed;
  zathura->ui.session->events.unknown_command = cb_unknown_command;
135

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
  /* zathura signals */
  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);

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

151
  /* page view */
Moritz Lipp's avatar
Moritz Lipp committed
152
  zathura->ui.page_widget = gtk_grid_new();
153
154
  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
155
  if (zathura->ui.page_widget == NULL) {
156
157
158
    goto error_free;
  }

159
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "size-allocate", G_CALLBACK(cb_view_resized), zathura);
160

161
  GtkAdjustment* hadjustment = gtk_scrolled_window_get_hadjustment(
162
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
163
164
165

  /* Connect hadjustment signals */
  g_signal_connect(G_OBJECT(hadjustment), "value-changed",
166
      G_CALLBACK(cb_view_hadjustment_value_changed), zathura);
167
168
169
170
  g_signal_connect(G_OBJECT(hadjustment), "changed",
      G_CALLBACK(cb_view_hadjustment_changed), zathura);

  GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(
171
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
172
173
174
175
176
177

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

179
  /* page view alignment */
180
181
  gtk_widget_set_halign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
  gtk_widget_set_valign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
182

183
184
185
186
  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);
187

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

190
  /* statusbar */
Moritz Lipp's avatar
Moritz Lipp committed
191
192
  zathura->ui.statusbar.file = girara_statusbar_item_add(zathura->ui.session, TRUE, TRUE, TRUE, NULL);
  if (zathura->ui.statusbar.file == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
193
    goto error_free;
Moritz Lipp's avatar
Moritz Lipp committed
194
195
  }

Moritz Lipp's avatar
Moritz Lipp committed
196
197
  zathura->ui.statusbar.buffer = girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
  if (zathura->ui.statusbar.buffer == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
198
    goto error_free;
Moritz Lipp's avatar
Moritz Lipp committed
199
200
  }

Moritz Lipp's avatar
Moritz Lipp committed
201
  zathura->ui.statusbar.page_number = girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
202
  if (zathura->ui.statusbar.page_number == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
203
    goto error_free;
Moritz Lipp's avatar
Moritz Lipp committed
204
205
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
206
  girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, _("[No name]"));
Moritz Lipp's avatar
Moritz Lipp committed
207

Moritz Lipp's avatar
Moritz Lipp committed
208
  /* signals */
209
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "destroy", G_CALLBACK(cb_destroy), zathura);
210

211
  /* database */
212
213
214
215
  char* database = NULL;
  girara_setting_get(zathura->ui.session, "database", &database);

  if (g_strcmp0(database, "plain") == 0) {
216
    girara_debug("Using plain database backend.");
217
218
219
    zathura->database = zathura_plaindatabase_new(zathura->config.data_dir);
#ifdef WITH_SQLITE
  } else if (g_strcmp0(database, "sqlite") == 0) {
220
    girara_debug("Using sqlite database backend.");
221
222
223
224
    char* tmp = g_build_filename(zathura->config.data_dir, "bookmarks.sqlite", NULL);
    zathura->database = zathura_sqldatabase_new(tmp);
    g_free(tmp);
#endif
Sebastian Ramacher's avatar
Sebastian Ramacher committed
225
  } else if (g_strcmp0(database, "null") != 0) {
226
227
228
    girara_error("Database backend '%s' is not supported.", database);
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
229
  if (zathura->database == NULL && g_strcmp0(database, "null") != 0) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
230
    girara_error("Unable to initialize database. Bookmarks won't be available.");
231
  } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
232
    g_object_set(G_OBJECT(zathura->ui.session->command_history), "io", zathura->database, NULL);
233
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
234
  g_free(database);
235

236
  /* bookmarks */
237
  zathura->bookmarks.bookmarks = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare,
Moritz Lipp's avatar
Moritz Lipp committed
238
                                 (girara_free_function_t) zathura_bookmark_free);
239

240
  /* jumplist */
241
242
  int jumplist_size = 20;
  girara_setting_get(zathura->ui.session, "jumplist-size", &jumplist_size);
243

244
245
  zathura->jumplist.max_size = jumplist_size < 0 ? 0 : jumplist_size;
  zathura->jumplist.list = NULL;
246
247
  zathura->jumplist.size = 0;
  zathura->jumplist.cur = NULL;
248

Sebastian Ramacher's avatar
Sebastian Ramacher committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
  /* CSS for index mode */
  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);

280
  /* Start D-Bus service */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
281
282
283
284
  bool dbus = true;
  girara_setting_get(zathura->ui.session, "dbus-service", &dbus);
  if (dbus == true) {
    zathura->dbus = zathura_dbus_new(zathura);
285
286
  }

287
  return true;
Moritz Lipp's avatar
Moritz Lipp committed
288
289
290

error_free:

Moritz Lipp's avatar
Moritz Lipp committed
291
  if (zathura->ui.page_widget != NULL) {
292
    g_object_unref(zathura->ui.page_widget);
293
294
  }

295
  return false;
Moritz Lipp's avatar
Moritz Lipp committed
296
297
298
299
300
301
302
303
304
}

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

Moritz Lipp's avatar
Moritz Lipp committed
305
  document_close(zathura, false);
306

307
  /* stop D-Bus */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
308
309
310
  if (zathura->dbus != NULL) {
    g_object_unref(zathura->dbus);
    zathura->dbus = NULL;
311
312
  }

Moritz Lipp's avatar
Moritz Lipp committed
313
314
315
316
  if (zathura->ui.session != NULL) {
    girara_session_destroy(zathura->ui.session);
  }

317
318
319
320
321
322
  /* stdin support */
  if (zathura->stdin_support.file != NULL) {
    g_unlink(zathura->stdin_support.file);
    g_free(zathura->stdin_support.file);
  }

323
324
325
  /* bookmarks */
  girara_list_free(zathura->bookmarks.bookmarks);

326
  /* database */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
327
328
329
  if (zathura->database != NULL) {
    g_object_unref(G_OBJECT(zathura->database));
  }
330

331
  /* free print settings */
Moritz Lipp's avatar
Moritz Lipp committed
332
  if (zathura->print.settings != NULL) {
333
334
335
336
337
338
    g_object_unref(zathura->print.settings);
  }

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

Moritz Lipp's avatar
Moritz Lipp committed
340
  /* free registered plugins */
Moritz Lipp's avatar
Moritz Lipp committed
341
  zathura_plugin_manager_free(zathura->plugins.manager);
342
343
344
345

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

348
349
350
351
352
353
354
355
356
  /* 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);
  }

357
  g_free(zathura);
358
359
}

360
#ifdef GDK_WINDOWING_X11
361
362
363
364
365
366
367
void
zathura_set_xid(zathura_t* zathura, Window xid)
{
  g_return_if_fail(zathura != NULL);

  zathura->ui.session->gtk.embed = xid;
}
368
#endif
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386

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
387
388
  g_return_if_fail(zathura != NULL);

389
390
391
392
393
394
395
  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
396
}
397

Sebastian Ramacher's avatar
Sebastian Ramacher committed
398
399
400
void
zathura_set_cache_dir(zathura_t* zathura, const char* dir)
{
401
  g_return_if_fail(zathura != NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
402
403
404
405
406
407
408
409

  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);
  }
410
411
412
413
414
415
416
417
418
419
420
}

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
421
    zathura_plugin_manager_add_dir(zathura->plugins.manager, path);
422
423
424
425
426
427
    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
428
    zathura_plugin_manager_add_dir(zathura->plugins.manager, path);
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    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;
}

444
static gchar*
445
prepare_document_open_from_stdin(zathura_t* zathura, const char* path)
446
447
448
{
  g_return_val_if_fail(zathura, NULL);

449
450
451
452
453
454
455
456
457
458
459
460
461
462
  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;
  }

463
464
465
  GError* error = NULL;
  gchar* file = NULL;
  gint handle = g_file_open_tmp("zathura.stdin.XXXXXX", &file, &error);
Moritz Lipp's avatar
Moritz Lipp committed
466
  if (handle == -1) {
467
468
469
470
    if (error != NULL) {
      girara_error("Can not create temporary file: %s", error->message);
      g_error_free(error);
    }
471
472
473
    return NULL;
  }

474
475
476
  // read and dump to temporary file
  if (infileno == -1) {
    girara_error("Can not read from file descriptor.");
477
478
479
480
481
482
483
484
    close(handle);
    g_unlink(file);
    g_free(file);
    return NULL;
  }

  char buffer[BUFSIZ];
  ssize_t count = 0;
485
  while ((count = read(infileno, buffer, BUFSIZ)) > 0) {
Moritz Lipp's avatar
Moritz Lipp committed
486
    if (write(handle, buffer, count) != count) {
487
488
489
490
491
492
493
      girara_error("Can not write to temporary file: %s", file);
      close(handle);
      g_unlink(file);
      g_free(file);
      return NULL;
    }
  }
Moritz Lipp's avatar
Moritz Lipp committed
494

495
496
  close(handle);

Moritz Lipp's avatar
Moritz Lipp committed
497
  if (count != 0) {
498
    girara_error("Can not read from file descriptor.");
499
500
501
502
503
504
505
506
    g_unlink(file);
    g_free(file);
    return NULL;
  }

  return file;
}

507
static gboolean
Moritz Lipp's avatar
Update    
Moritz Lipp committed
508
509
510
511
512
document_info_open(gpointer data)
{
  zathura_document_info_t* document_info = data;
  g_return_val_if_fail(document_info != NULL, FALSE);

513
  if (document_info->zathura != NULL && document_info->path != NULL) {
514
    char* file = NULL;
515
516
517
    if (g_strcmp0(document_info->path, "-") == 0 ||
        g_str_has_prefix(document_info->path, "/proc/self/fd/") == true) {
      file = prepare_document_open_from_stdin(document_info->zathura, document_info->path);
518
519
      if (file == NULL) {
        girara_notify(document_info->zathura->ui.session, GIRARA_ERROR,
520
                      _("Could not read file from stdin and write it to a temporary file."));
521
522
      } else {
        document_info->zathura->stdin_support.file = g_strdup(file);
523
524
525
526
527
528
      }
    } else {
      file = g_strdup(document_info->path);
    }

    if (file != NULL) {
529
530
531
532
533
534
535
      if (document_info->synctex != NULL) {
        document_open_synctex(document_info->zathura, file,
                              document_info->password, document_info->synctex);
      } else {
        document_open(document_info->zathura, file, document_info->password,
                      document_info->page_number);
      }
536
      g_free(file);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
537
538
539
540
541
542
543
544
545
546
547
548

      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);
        }
      }
549
    }
Moritz Lipp's avatar
Update    
Moritz Lipp committed
550
551
  }

552
  g_free(document_info);
Moritz Lipp's avatar
Update    
Moritz Lipp committed
553
  return FALSE;
554
555
}

556
static char*
557
get_formatted_filename(zathura_t* zathura, const char* file_path, bool statusbar)
558
559
{
  bool basename_only = false;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
560
  if (statusbar == true) {
561
562
563
564
565
    girara_setting_get(zathura->ui.session, "window-title-basename", &basename_only);
  } else {
    girara_setting_get(zathura->ui.session, "statusbar-basename", &basename_only);
  }

566
567
  if (basename_only == false) {
    bool home_tilde = false;
568
569
570
571
572
573
    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
574
    const size_t file_path_len = file_path ? strlen(file_path) : 0;
575

Sebastian Ramacher's avatar
Sebastian Ramacher committed
576
577
578
    if (home_tilde == true) {
      char* home = girara_get_home_directory(NULL);
      const size_t home_len = home ? strlen(home) : 0;
579

Sidharth Kapur's avatar
Sidharth Kapur committed
580
581
582
583
      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
584
        g_free(home);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
585
        return g_strdup_printf("~%s", &file_path[home_len]);
586
      } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
587
        g_free(home);
588
        return g_strdup(file_path);
589
590
      }
    } else {
591
      return g_strdup(file_path);
592
593
    }
  } else {
594
    const char* basename = zathura_document_get_basename(zathura->document);
595
    return g_strdup(basename);
596
597
598
  }
}

Moritz Lipp's avatar
Moritz Lipp committed
599
bool
600
601
document_open(zathura_t* zathura, const char* path, const char* password,
              int page_number)
Moritz Lipp's avatar
Moritz Lipp committed
602
{
603
  if (zathura == NULL || zathura->plugins.manager == NULL || path == NULL) {
604
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
605
606
  }

607
  gchar* file_uri = NULL;
608
609
  zathura_error_t error = ZATHURA_ERROR_OK;
  zathura_document_t* document = zathura_document_open(zathura->plugins.manager, path, password, &error);
Moritz Lipp's avatar
Moritz Lipp committed
610

Moritz Lipp's avatar
Moritz Lipp committed
611
  if (document == NULL) {
612
613
614
615
616
617
618
619
    if (error == ZATHURA_ERROR_INVALID_PASSWORD) {
      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;

        if (path != NULL) {
          password_dialog_info->path = g_strdup(path);
          girara_dialog(zathura->ui.session, "Enter password:", true, NULL,
Moritz Lipp's avatar
Moritz Lipp committed
620
                        (girara_callback_inputbar_activate_t) cb_password_dialog, password_dialog_info);
621
622
623
624
625
626
627
          goto error_out;
        } else {
          free(password_dialog_info);
        }
      }
      goto error_out;
    }
Moritz Lipp's avatar
Moritz Lipp committed
628
629
630
    if (error == ZATHURA_ERROR_OK ) {
      girara_notify(zathura->ui.session, GIRARA_ERROR, _("Unsupported file type. Please install the necessary plugin."));
    }
631
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
632
633
  }

634
635
  const char* file_path        = zathura_document_get_path(document);
  unsigned int number_of_pages = zathura_document_get_number_of_pages(document);
636

637
638
639
640
641
642
  if (number_of_pages == 0) {
    girara_notify(zathura->ui.session, GIRARA_WARNING,
        _("Document does not contain any pages"));
    goto error_free;
  }

643
  /* read history file */
644
645
646
647
648
649
650
651
652
653
  zathura_fileinfo_t file_info = {
    .current_page = 0,
    .page_offset = 0,
    .scale = 1,
    .rotation = 0,
    .pages_per_row = 0,
    .first_page_column = 0,
    .position_x = 0,
    .position_y = 0
  };
654
655
656
657
  bool known_file = false;
  if (zathura->database != NULL) {
    known_file = zathura_db_get_fileinfo(zathura->database, file_path, &file_info);
  }
658

Moritz Lipp's avatar
Moritz Lipp committed
659
  /* set page offset */
660
  zathura_document_set_page_offset(document, file_info.page_offset);
661

662
  /* check for valid scale value */
663
664
665
  if (file_info.scale <= DBL_EPSILON) {
    file_info.scale = 1;
  }
Moritz Lipp's avatar
Moritz Lipp committed
666
667
  zathura_document_set_scale(document,
      zathura_correct_scale_value(zathura->ui.session, file_info.scale));
668
669

  /* check current page number */
670
671
672
673
674
675
  /* 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) {
676
677
    girara_warning("document info: '%s' has an invalid page number", file_path);
    zathura_document_set_current_page_number(document, 0);
678
  } else {
679
    zathura_document_set_current_page_number(document, page_number);
680
681
682
  }

  /* check for valid rotation */
683
  if (file_info.rotation % 90 != 0) {
684
685
686
    girara_warning("document info: '%s' has an invalid rotation", file_path);
    zathura_document_set_rotation(document, 0);
  } else {
687
    zathura_document_set_rotation(document, file_info.rotation % 360);
688
689
690
691
692
693
694
695
696
697
698
  }

  /* 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";
699
  if (known_file == false && girara_setting_get(zathura->ui.session, "adjust-open", &(adjust_open)) == true) {
700
701
702
703
704
705
706
707
    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);
708
709
  } else {
    zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_NONE);
710
711
  }

712
713
714
715
716
  /* 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;

717
  /* update statusbar */
718
719
720
  char* filename = get_formatted_filename(zathura, file_path, true);
  girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, filename);
  g_free(filename);
721

Moritz Lipp's avatar
Moritz Lipp committed
722
  /* install file monitor */
723
  file_uri = g_filename_to_uri(file_path, NULL, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
724
725
726
727
728
  if (file_uri == NULL) {
    goto error_free;
  }

  if (zathura->file_monitor.file == NULL) {
729
730
731
732
    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
733
734
735
  }

  if (zathura->file_monitor.monitor == NULL) {
736
737
738
739
740
    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
741
742
743
  }

  if (zathura->file_monitor.file_path == NULL) {
744
    zathura->file_monitor.file_path = g_strdup(file_path);
745
746
747
    if (zathura->file_monitor.file_path == NULL) {
      goto error_free;
    }
Moritz Lipp's avatar
Moritz Lipp committed
748
749
  }

750
  if (password != NULL) {
751
    g_free(zathura->file_monitor.password);
752
    zathura->file_monitor.password = g_strdup(password);
Moritz Lipp's avatar
Moritz Lipp committed
753
754
755
756
757
    if (zathura->file_monitor.password == NULL) {
      goto error_free;
    }
  }

Moritz Lipp's avatar
Moritz Lipp committed
758
759
760
761
762
763
  /* create marks list */
  zathura->global.marks = girara_list_new2((girara_free_function_t) mark_free);
  if (zathura->global.marks == NULL) {
    goto error_free;
  }

Moritz Lipp's avatar
Moritz Lipp committed
764
  zathura->document = document;
Moritz Lipp's avatar
Moritz Lipp committed
765

766
767
768
769
770
771
772
773
774
  /* 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;
  }

775
  /* threads */
776
  zathura->sync.render_thread = zathura_renderer_new(cache_size);
777
778
779
780
781

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
782
  /* set up recolor info in ZathuraRenderer */
783
784
785
786
787
788
789
790
791
  char* recolor_dark = NULL;
  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);

792
793
794
795
796
  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);
797
798
  girara_setting_get(zathura->ui.session, "recolor-reverse-video", &recolor);
  zathura_renderer_enable_recolor_reverse_video(zathura->sync.render_thread, recolor);
799

800
801
802
803
804
805
  /* 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));

806
  const unsigned int view_width = (unsigned int)floor(gtk_adjustment_get_page_size(hadjustment));
807
  zathura_document_set_viewport_width(zathura->document, view_width);
808
  const unsigned int view_height = (unsigned int)floor(gtk_adjustment_get_page_size(vadjustment));
809
810
  zathura_document_set_viewport_height(zathura->document, view_height);

811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
  /* 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
828
    g_object_ref(page_widget);
829
830
    zathura->pages[page_id] = page_widget;

831
832
833
    gtk_widget_set_halign(page_widget, GTK_ALIGN_CENTER);
    gtk_widget_set_valign(page_widget, GTK_ALIGN_CENTER);

834
835
836
    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",
837
        G_CALLBACK(cb_page_widget_image_selected), zathura);
838
839
840
841
    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);
842
843
    g_signal_connect(G_OBJECT(page_widget), "scaled-button-release",
        G_CALLBACK(cb_page_widget_scaled_button_release), zathura);
844
845
  }

846
  /* view mode */
847
848
849
850
851
852
  unsigned int pages_per_row = 1;
  unsigned int first_page_column = 1;
  unsigned int page_padding = 1;

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

853
854
855
856
857
  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
858

859
860
861
862
863
864
  if (file_info.first_page_column > 0) {
    first_page_column = file_info.first_page_column;
  } else {
    girara_setting_get(zathura->ui.session, "first-page-column", &first_page_column);
  }

Moritz Lipp's avatar
Moritz Lipp committed
865
  girara_setting_set(zathura->ui.session, "pages-per-row", &pages_per_row);
866
  girara_setting_set(zathura->ui.session, "first-page-column", &first_page_column);
867
868
869

  page_widget_set_mode(zathura, page_padding, pages_per_row, first_page_column);
  zathura_document_set_page_layout(zathura->document, page_padding, pages_per_row, first_page_column);
Moritz Lipp's avatar
Moritz Lipp committed
870

871
  girara_set_view(zathura->ui.session, zathura->ui.page_widget);
Moritz Lipp's avatar
Moritz Lipp committed
872

Moritz Lipp's avatar
Moritz Lipp committed
873

874
  /* bookmarks */
875
876
  if (zathura->database != NULL) {
    if (zathura_bookmarks_load(zathura, file_path) == false) {
877
      girara_debug("Failed to load bookmarks.");
878
    }
879

880
881
882
883
    /* jumplist */
    if (zathura_jumplist_load(zathura, file_path) == false) {
      zathura->jumplist.list = girara_list_new2(g_free);
    }
884
885
  }

Moritz Lipp's avatar
Moritz Lipp committed
886
  /* update title */
887
888
889
  char* formatted_filename = get_formatted_filename(zathura, file_path, false);
  girara_set_window_title(zathura->ui.session, formatted_filename);
  g_free(formatted_filename);
Moritz Lipp's avatar
Moritz Lipp committed
890

891
  g_free(file_uri);
Moritz Lipp's avatar
Moritz Lipp committed
892

893
  /* adjust_view */
894
  adjust_view(zathura);
895
896
897
898
899
900
901
902
903
  for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
    /* set widget size */
    zathura_page_t* page = zathura_document_get_page(document, page_id);
    unsigned int page_height = 0;
    unsigned int page_width  = 0;

    /* adjust_view calls render_all in some cases and render_all calls
     * gtk_widget_set_size_request. To be sure that it's really called, do it
     * here once again. */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
904
905
    const double height = zathura_page_get_height(page);
    const double width = zathura_page_get_width(page);
906
907
908
909
910
911
    page_calc_height_width(zathura->document, height, width, &page_height, &page_width, true);
    gtk_widget_set_size_request(zathura->pages[page_id], page_width, page_height);

    /* show widget */
    gtk_widget_show(zathura->pages[page_id]);
  }
912

913
  /* Set page */
914
  page_set(zathura, zathura_document_get_current_page_number(document));
915
916

  /* Set position (only if restoring from history file) */
Sebastian Ramacher's avatar
CS    
Sebastian Ramacher committed
917
918
919
  if (file_info.current_page == zathura_document_get_current_page_number(document) &&
      (file_info.position_x != 0 || file_info.position_y != 0)) {
    position_set(zathura, file_info.position_x, file_info.position_y);
920
921
  }

922
923
  update_visible_pages(zathura);

Moritz Lipp's avatar
Moritz Lipp committed
924
  return true;
925
926
927

error_free:

Moritz Lipp's avatar
Moritz Lipp committed
928
929
930
931
  if (file_uri != NULL) {
    g_free(file_uri);
  }

932
933
934
935
936
  zathura_document_free(document);

error_out:

  return false;
Moritz Lipp's avatar
Moritz Lipp committed
937
938
}

939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
bool
document_open_synctex(zathura_t* zathura, const char* path,
                      const char* password, const char* synctex)
{
  bool ret = document_open(zathura, path, password,
                           ZATHURA_PAGE_NUMBER_UNSPECIFIED);
  if (ret == false) {
    return false;
  }
  if (synctex == NULL) {
    return true;
  }

  int line = 0;
  int column = 0;
  char* input_file = NULL;
  if (synctex_parse_input(synctex, &input_file, &line, &column) == false) {
    return false;
  }

  ret = synctex_view(zathura, input_file, line, column);
  g_free(input_file);
  return ret;
}

964
void
965
document_open_idle(zathura_t* zathura, const char* path, const char* password,
966
                   int page_number, const char* mode, const char* synctex)
967
968
969
970
971
{
  if (zathura == NULL || path == NULL) {
    return;
  }

972
973
974
975
  zathura_document_info_t* document_info = g_try_malloc0(sizeof(zathura_document_info_t));
  if (document_info == NULL) {
    return;
  }
976

977
978
979
980
  document_info->zathura     = zathura;
  document_info->path        = path;
  document_info->password    = password;
  document_info->page_number = page_number;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
981
  document_info->mode        = mode;
982
  document_info->synctex     = synctex;
983
984
985
986

  gdk_threads_add_idle(document_info_open, document_info);
}

987
988
989
990
991
992
993
994
bool
document_save(zathura_t* zathura, const char* path, bool overwrite)
{
  g_return_val_if_fail(zathura, false);
  g_return_val_if_fail(zathura->document, false);
  g_return_val_if_fail(path, false);

  gchar* file_path = girara_fix_path(path);
995
996
997
998
999
1000
1001
1002
1003
  /* use current basename if path points to a directory  */
  if (g_file_test(file_path, G_FILE_TEST_IS_DIR) == TRUE) {
    char* basename = g_path_get_basename(zathura_document_get_path(zathura->document));
    char* tmp = file_path;
    file_path = g_strconcat(file_path, "/", basename, NULL);
    g_free(tmp);
    g_free(basename);
  }

Moritz Lipp's avatar
Moritz Lipp committed
1004
  if ((overwrite == false) && g_file_test(file_path, G_FILE_TEST_EXISTS)) {
1005
    girara_error("File already exists: %s. Use :write! to overwrite it.", file_path);
1006
    g_free(file_path);
1007
1008
1009
    return false;
  }

1010
  zathura_error_t error = zathura_document_save_as(zathura->document, file_path);
1011
  g_free(file_path);
1012

1013
  return (error == ZATHURA_ERROR_OK) ? true : false;
1014
1015
}

Pavel Borzenkov's avatar
Pavel Borzenkov committed
1016
static void
1017
remove_page_from_table(GtkWidget* page, gpointer UNUSED(permanent))
Pavel Borzenkov's avatar
Pavel Borzenkov committed
1018
{
1019
  gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(page)), page);
Pavel Borzenkov's avatar
Pavel Borzenkov committed
1020
1021
}

Moritz Lipp's avatar
Moritz Lipp committed
1022
bool
Moritz Lipp's avatar
Moritz Lipp committed
1023
document_close(zathura_t* zathura, bool keep_monitor)
Moritz Lipp's avatar
Moritz Lipp committed
1024
{
Moritz Lipp's avatar
Moritz Lipp committed
1025
  if (zathura == NULL || zathura->document == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
1026
1027
1028
    return false;
  }

1029
1030
1031
  /* stop rendering */
  zathura_renderer_stop(zathura->sync.render_thread);

Moritz Lipp's avatar
Moritz Lipp committed
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
  /* remove monitor */
  if (keep_monitor == false) {
    if (zathura->file_monitor.monitor != NULL) {
      g_file_monitor_cancel(zathura->file_monitor.monitor);
      g_object_unref(zathura->file_monitor.monitor);
      zathura->file_monitor.monitor = NULL;
    }

    if (zathura->file_monitor.file != NULL) {
      g_object_unref(zathura->file_monitor.file);
      zathura->file_monitor.file = NULL;
    }

    if (zathura->file_monitor.file_path != NULL) {
      g_free(zathura->file_monitor.file_path);
      zathura->file_monitor.file_path = NULL;
    }

    if (zathura->file_monitor.password != NULL) {
      g_free(zathura->file_monitor.password);
      zathura->file_monitor.password = NULL;
    }
  }

Moritz Lipp's avatar
Moritz Lipp committed
1056
1057
1058
1059
1060
1061
  /* remove marks */
  if (zathura->global.marks != NULL) {
    girara_list_free(zathura->global.marks);
    zathura->global.marks = NULL;
  }

1062
1063
  /* store file information */
  const char* path = zathura_document_get_path(zathura->document);
1064