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

3
#include <errno.h>
Moritz Lipp's avatar
Moritz Lipp committed
4
#include <stdlib.h>
5
#include <math.h>
6
#include <string.h>
Moritz Lipp's avatar
Moritz Lipp committed
7

8 9 10 11 12
#include <girara/datastructures.h>
#include <girara/utils.h>
#include <girara/session.h>
#include <girara/statusbar.h>
#include <girara/settings.h>
13
#include <girara/shortcuts.h>
14
#include <girara/template.h>
15
#include <glib/gstdio.h>
16
#include <glib/gi18n.h>
17

18 19 20 21
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/gdkwayland.h>
#endif

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

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

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

58

59
static gboolean document_info_open(gpointer data);
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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78
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
79
/* function implementation */
Moritz Lipp's avatar
Moritz Lipp committed
80
zathura_t*
81
zathura_create(void)
82
{
83 84 85 86
  zathura_t* zathura = g_try_malloc0(sizeof(zathura_t));
  if (zathura == NULL) {
    return NULL;
  }
87

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

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

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

103 104 105
  /* set default icon */
  girara_setting_set(zathura->ui.session, "window-icon", "org.pwmt.zathura");

Moritz Lipp's avatar
Moritz Lipp committed
106 107 108 109 110
#ifdef G_OS_UNIX
  /* signal handler */
  zathura->signals.sigterm = g_unix_signal_add(SIGTERM, zathura_signal_sigterm, zathura);
#endif

111 112 113
  /* MIME type detection */
  zathura->content_type_context = zathura_content_type_new();

114 115 116 117 118 119 120 121 122 123
  zathura->ui.session->global.data = zathura;

  return zathura;

error_out:

  zathura_free(zathura);

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

125 126
static void
create_directories(zathura_t* zathura)
127
{
128
  static const unsigned int mode = 0700;
129

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

135 136 137
  if (g_mkdir_with_parents(zathura->config.data_dir, mode) == -1) {
    girara_error("Could not create '%s': %s", zathura->config.data_dir,
                 strerror(errno));
138
  }
139
}
140

141
void
142
zathura_update_view_ppi(zathura_t* zathura)
143 144 145 146 147 148 149 150 151 152 153 154 155 156
{
  if (zathura == NULL) {
    return;
  }

  /* get view widget GdkMonitor */
  GdkWindow* window = gtk_widget_get_window (zathura->ui.session->gtk.view); // NULL if not realized
  if (window == NULL) {
    return;
  }
  GdkDisplay* display = gtk_widget_get_display(zathura->ui.session->gtk.view);
  if (display == NULL) {
    return;
  }
157

158
  double ppi = 0.0;
159

160 161 162 163 164 165
  GdkMonitor* monitor = gdk_display_get_monitor_at_window(display, window);
  if (monitor == NULL) {
    return;
  }

  /* physical width of monitor */
166
  const int width_mm = gdk_monitor_get_width_mm(monitor);
167 168 169 170 171

  /* size of monitor in pixels */
  GdkRectangle monitor_geom;
  gdk_monitor_get_geometry(monitor, &monitor_geom);

172
  /* calculate ppi, knowing that 1 inch = 25.4 mm */
173
  if (width_mm == 0) {
174
    girara_debug("cannot calculate PPI: monitor has zero width");
175
  } else {
176
    ppi = monitor_geom.width * 25.4 / width_mm;
177 178 179
  }

#ifdef GDK_WINDOWING_WAYLAND
Jeremie Knuesel's avatar
Jeremie Knuesel committed
180 181 182 183 184 185 186
  /* work around apparent bug in GDK: on Wayland, monitor geometry doesn't
   * return values in application pixels as documented, but in device pixels.
   * */
  if (GDK_IS_WAYLAND_DISPLAY(display))
  {
    /* not using the cached value for the scale factor here to avoid issues
     * if this function is called before the cached value is updated */
187
    const int device_factor = gtk_widget_get_scale_factor(zathura->ui.session->gtk.view);
Jeremie Knuesel's avatar
Jeremie Knuesel committed
188 189 190
    girara_debug("on Wayland, correcting PPI for device scale factor = %d", device_factor);
    if (device_factor != 0) {
      ppi /= device_factor;
191
    }
Jeremie Knuesel's avatar
Jeremie Knuesel committed
192
  }
193 194
#endif

195
  const double current_ppi = zathura_document_get_viewport_ppi(zathura->document);
196
  if (fabs(ppi - current_ppi) > DBL_EPSILON) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
197
    girara_debug("monitor width: %d mm, pixels: %d, ppi: %0.2f", width_mm, monitor_geom.width, ppi);
198 199 200 201
    zathura_document_set_viewport_ppi(zathura->document, ppi);
    render_all(zathura);
    refresh_view(zathura);
  }
202 203
}

204 205 206
static bool
init_ui(zathura_t* zathura)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
207
  if (girara_session_init(zathura->ui.session, "zathura") == false) {
208
    return false;
209 210 211
  }

  /* girara events */
Moritz Lipp's avatar
Moritz Lipp committed
212 213
  zathura->ui.session->events.buffer_changed  = cb_buffer_changed;
  zathura->ui.session->events.unknown_command = cb_unknown_command;
214

215
  /* zathura signals */
216 217 218
  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);
219 220 221 222

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

223 224
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.view),
      "notify::scale-factor", G_CALLBACK(cb_scale_factor), zathura);
225

226 227 228
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.view),
      "screen-changed", G_CALLBACK(cb_widget_screen_changed), zathura);

229 230 231
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window),
      "configure-event", G_CALLBACK(cb_widget_configured), zathura);

232 233 234
  /* initialize the screen-changed handler to 0 (i.e. invalid) */
  zathura->signals.monitors_changed_handler = 0;

235
  /* page view */
236
  zathura->ui.page_widget = gtk_grid_new();
237 238
  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
239
  if (zathura->ui.page_widget == NULL) {
240
    return false;
241 242
  }

243 244
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "size-allocate",
                   G_CALLBACK(cb_view_resized), zathura);
245

246
  GtkAdjustment* hadjustment = gtk_scrolled_window_get_hadjustment(
247
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
248 249 250

  /* Connect hadjustment signals */
  g_signal_connect(G_OBJECT(hadjustment), "value-changed",
251
                   G_CALLBACK(cb_view_hadjustment_value_changed), zathura);
252
  g_signal_connect(G_OBJECT(hadjustment), "changed",
253
                   G_CALLBACK(cb_view_hadjustment_changed), zathura);
254 255

  GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(
256
                 GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
257 258 259

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

264
  /* page view alignment */
265 266
  gtk_widget_set_halign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
  gtk_widget_set_valign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
267

268 269 270 271
  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);
272

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

275
  /* statusbar */
276 277
  zathura->ui.statusbar.file =
    girara_statusbar_item_add(zathura->ui.session, TRUE, TRUE, TRUE, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
278
  if (zathura->ui.statusbar.file == NULL) {
279
    return false;
Moritz Lipp's avatar
Moritz Lipp committed
280 281
  }

282 283
  zathura->ui.statusbar.buffer =
    girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
284
  if (zathura->ui.statusbar.buffer == NULL) {
285
    return false;
Moritz Lipp's avatar
Moritz Lipp committed
286 287
  }

288 289
  zathura->ui.statusbar.page_number =
    girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
290
  if (zathura->ui.statusbar.page_number == NULL) {
291
    return false;
Moritz Lipp's avatar
Moritz Lipp committed
292 293
  }

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

Moritz Lipp's avatar
Moritz Lipp committed
297
  /* signals */
298 299
  g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "destroy",
                   G_CALLBACK(cb_destroy), zathura);
300

301 302 303 304 305 306 307 308 309
  return true;
}

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

310
  static const char index_settings[][16] = {
311 312 313 314 315 316 317 318 319 320 321 322 323
    "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) {
324
      parse_color(&rgba, tmp_value);
325 326 327 328 329 330 331 332
      g_free(tmp_value);
    }

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
333
  GResource* css_resource = zathura_resources_get_resource();
334 335 336 337 338
  GBytes* css_data = g_resource_lookup_data(css_resource,
                                            "/org/pwmt/zathura/CSS/zathura.css_t",
                                            G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
  if (css_data != NULL) {
    char* css = g_strdup_printf("%s\n%s", girara_template_get_base(csstemplate),
Sebastian Ramacher's avatar
Sebastian Ramacher committed
339
                                (const char*) g_bytes_get_data(css_data, NULL));
340 341 342 343
    girara_template_set_base(csstemplate, css);
    g_free(css);
    g_bytes_unref(css_data);
  }
344 345 346 347 348
}

static void
init_database(zathura_t* zathura)
{
349 350 351 352
  char* database = NULL;
  girara_setting_get(zathura->ui.session, "database", &database);

  if (g_strcmp0(database, "plain") == 0) {
353
    girara_debug("Using plain database backend.");
354 355 356
    zathura->database = zathura_plaindatabase_new(zathura->config.data_dir);
#ifdef WITH_SQLITE
  } else if (g_strcmp0(database, "sqlite") == 0) {
357
    girara_debug("Using sqlite database backend.");
358 359
    char* tmp =
      g_build_filename(zathura->config.data_dir, "bookmarks.sqlite", NULL);
360 361 362
    zathura->database = zathura_sqldatabase_new(tmp);
    g_free(tmp);
#endif
363
  } else if (g_strcmp0(database, "null") != 0) {
364 365 366
    girara_error("Database backend '%s' is not supported.", database);
  }

367
  if (zathura->database == NULL && g_strcmp0(database, "null") != 0) {
368 369 370 371 372 373
    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);
374
  }
375
  g_free(database);
376
}
377

378 379 380
static void
init_jumplist(zathura_t* zathura)
{
381 382
  int jumplist_size = 20;
  girara_setting_get(zathura->ui.session, "jumplist-size", &jumplist_size);
383

384
  zathura->jumplist.max_size = jumplist_size < 0 ? 0 : jumplist_size;
385 386 387 388
  zathura->jumplist.list     = NULL;
  zathura->jumplist.size     = 0;
  zathura->jumplist.cur      = NULL;
}
389

390 391 392 393 394
static void
init_shortcut_helpers(zathura_t* zathura)
{
  zathura->shortcut.mouse.x = 0;
  zathura->shortcut.mouse.y = 0;
395

396
  zathura->shortcut.toggle_page_mode.pages = 2;
397

398 399 400 401
  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;
}
402

403 404 405 406 407 408
bool
zathura_init(zathura_t* zathura)
{
  if (zathura == NULL) {
    return false;
  }
409

410 411 412 413 414 415 416 417 418 419 420 421 422
  /* 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;
423 424
  }

425 426
  /* database */
  init_database(zathura);
427

428 429 430 431
  /* bookmarks */
  zathura->bookmarks.bookmarks = girara_sorted_list_new2(
    (girara_compare_function_t)zathura_bookmarks_compare,
    (girara_free_function_t)zathura_bookmark_free);
432

433 434
  /* jumplist */
  init_jumplist(zathura);
435

436 437 438 439 440
  /* CSS for index mode */
  init_css(zathura);

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

442
  /* Start D-Bus service */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
443 444 445 446
  bool dbus = true;
  girara_setting_get(zathura->ui.session, "dbus-service", &dbus);
  if (dbus == true) {
    zathura->dbus = zathura_dbus_new(zathura);
447 448
  }

449
  return true;
Moritz Lipp's avatar
Moritz Lipp committed
450 451 452

error_free:

Moritz Lipp's avatar
Moritz Lipp committed
453
  if (zathura->ui.page_widget != NULL) {
454
    g_object_unref(zathura->ui.page_widget);
455 456
  }

457
  return false;
Moritz Lipp's avatar
Moritz Lipp committed
458 459 460 461 462 463 464 465 466
}

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

Moritz Lipp's avatar
Moritz Lipp committed
467
  document_close(zathura, false);
468

469 470 471
  /* MIME type detection */
  zathura_content_type_free(zathura->content_type_context);

472 473 474 475 476 477 478
#ifdef G_OS_UNIX
  if (zathura->signals.sigterm > 0) {
    g_source_remove(zathura->signals.sigterm);
    zathura->signals.sigterm = 0;
  }
#endif

479
  /* stop D-Bus */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
480
  g_clear_object(&zathura->dbus);
481

Moritz Lipp's avatar
Moritz Lipp committed
482 483 484 485
  if (zathura->ui.session != NULL) {
    girara_session_destroy(zathura->ui.session);
  }

486
  /* shortcut */
487 488 489
  if (zathura->shortcut.toggle_presentation_mode.first_page_column_list != NULL) {
    g_free(zathura->shortcut.toggle_presentation_mode.first_page_column_list);
  }
490

491 492 493 494 495 496
  /* stdin support */
  if (zathura->stdin_support.file != NULL) {
    g_unlink(zathura->stdin_support.file);
    g_free(zathura->stdin_support.file);
  }

497 498 499
  /* bookmarks */
  girara_list_free(zathura->bookmarks.bookmarks);

500
  /* database */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
501
  g_clear_object(&zathura->database);
502

503
  /* free print settings */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
504 505
  g_clear_object(&zathura->print.settings);
  g_clear_object(&zathura->print.page_setup);
506

Moritz Lipp's avatar
Moritz Lipp committed
507
  /* free registered plugins */
Moritz Lipp's avatar
Moritz Lipp committed
508
  zathura_plugin_manager_free(zathura->plugins.manager);
509 510 511 512

  /* free config variables */
  g_free(zathura->config.config_dir);
  g_free(zathura->config.data_dir);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
513
  g_free(zathura->config.cache_dir);
514

515 516 517 518 519
  /* free jumplist */
  if (zathura->jumplist.cur != NULL) {
    girara_list_iterator_free(zathura->jumplist.cur);
  }

520 521 522 523
  if (zathura->jumplist.list != NULL) {
    girara_list_free(zathura->jumplist.list);
  }

524
  g_free(zathura);
525 526
}

527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
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
552 553
  g_return_if_fail(zathura != NULL);

554 555 556 557 558 559 560
  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
561
}
562

Sebastian Ramacher's avatar
Sebastian Ramacher committed
563 564 565
void
zathura_set_cache_dir(zathura_t* zathura, const char* dir)
{
566
  g_return_if_fail(zathura != NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
567 568 569 570 571 572 573 574

  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);
  }
575 576
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
static void
add_dir(void* data, void* userdata)
{
  const char* path                         = data;
  zathura_plugin_manager_t* plugin_manager = userdata;

  zathura_plugin_manager_add_dir(plugin_manager, path);
}

static void
set_plugin_dir(zathura_t* zathura, const char* dir)
{
  girara_list_t* paths = girara_split_path_array(dir);
  girara_list_foreach(paths, add_dir, zathura->plugins.manager);
  girara_list_free(paths);
}

594 595 596 597 598 599 600
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) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
601
    set_plugin_dir(zathura, dir);
602
#ifdef ZATHURA_PLUGINDIR
Sebastian Ramacher's avatar
Sebastian Ramacher committed
603 604
  } else {
    set_plugin_dir(zathura, ZATHURA_PLUGINDIR);
605 606 607 608 609 610 611 612 613 614 615 616
#endif
  }
}

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

  zathura->global.arguments = argv;
}

617
#ifdef G_OS_UNIX
618
static gchar*
Sebastian Ramacher's avatar
Sebastian Ramacher committed
619
prepare_document_open_from_stdin(const char* path)
620
{
621 622 623 624 625 626 627 628 629 630 631 632 633 634
  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
635 636 637 638 639
  if (infileno == -1) {
    girara_error("Can not read from file descriptor.");
    return NULL;
  }

640 641 642 643 644 645 646 647 648 649 650
  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) {
651 652 653 654
    if (error != NULL) {
      girara_error("Can not create temporary file: %s", error->message);
      g_error_free(error);
    }
655
    g_object_unref(input_stream);
656 657 658
    return NULL;
  }

659 660 661 662 663 664 665 666 667
  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);
668
    }
669 670
    g_file_delete(tmpfile, NULL, NULL);
    g_object_unref(tmpfile);
671 672 673
    return NULL;
  }

674 675 676
  char* file = g_file_get_path(tmpfile);
  g_object_unref(tmpfile);

677 678
  return file;
}
679
#endif
680

Lukas K.'s avatar
Lukas K. committed
681
static gchar*
Sebastian Ramacher's avatar
Sebastian Ramacher committed
682
prepare_document_open_from_gfile(GFile* source)
Lukas K.'s avatar
Lukas K. committed
683
{
684
  gchar*         file     = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
685
  GFileIOStream* iostream = NULL;
686
  GError*        error    = NULL;
Lukas K.'s avatar
Lukas K. committed
687

688
  GFile* tmpfile = g_file_new_tmp("zathura.gio.XXXXXX", &iostream, &error);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
689
  if (tmpfile == NULL) {
Lukas K.'s avatar
Lukas K. committed
690 691 692 693 694 695 696
    if (error != NULL) {
      girara_error("Can not create temporary file: %s", error->message);
      g_error_free(error);
    }
    return NULL;
  }

697 698
  gboolean rc = g_file_copy(source, tmpfile, G_FILE_COPY_OVERWRITE, NULL, NULL,
                            NULL, &error);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
699
  if (rc == FALSE) {
Lukas K.'s avatar
Lukas K. committed
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
    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;
}

716
static gboolean
Moritz Lipp's avatar
Moritz Lipp committed
717 718 719 720
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
721
  char* uri = NULL;
Moritz Lipp's avatar
Moritz Lipp committed
722

723
  if (document_info->zathura != NULL && document_info->path != NULL) {
724
    char* file = NULL;
725 726
    if (g_strcmp0(document_info->path, "-") == 0 ||
        g_str_has_prefix(document_info->path, "/proc/self/fd/") == true) {
727
#ifdef G_OS_UNIX
Sebastian Ramacher's avatar
Sebastian Ramacher committed
728
      file = prepare_document_open_from_stdin(document_info->path);
729
#endif
730 731
      if (file == NULL) {
        girara_notify(document_info->zathura->ui.session, GIRARA_ERROR,
732
                      _("Could not read file from stdin and write it to a temporary file."));
733 734
      } else {
        document_info->zathura->stdin_support.file = g_strdup(file);
735 736
      }
    } else {
737 738 739 740
      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
741 742
      }
      else {
743
        /* copy file with GIO */
Lukas K.'s avatar
Lukas K. committed
744
        uri = g_file_get_uri(gf);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
745
        file = prepare_document_open_from_gfile(gf);
746 747 748
        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
749
        } else {
750
          document_info->zathura->stdin_support.file = g_strdup(file);
Lukas K.'s avatar
Lukas K. committed
751 752 753
        }
      }
      g_object_unref(gf);
754 755 756
    }

    if (file != NULL) {
757
      if (document_info->synctex != NULL) {
Lukas K.'s avatar
Lukas K. committed
758
        document_open_synctex(document_info->zathura, file, uri, 
759 760
                              document_info->password, document_info->synctex);
      } else {
Lukas K.'s avatar
Lukas K. committed
761
        document_open(document_info->zathura, file, uri, document_info->password,
762 763
                      document_info->page_number);
      }
764
      g_free(file);
Lukas K.'s avatar
Lukas K. committed
765
      g_free(uri);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
766 767 768 769 770 771 772 773 774 775 776 777

      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);
        }
      }
778
    }
Moritz Lipp's avatar
Moritz Lipp committed
779 780
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
781
  free_document_info(document_info);
Moritz Lipp's avatar
Moritz Lipp committed
782
  return FALSE;
783 784
}

Lukas K.'s avatar
Lukas K. committed
785 786
char*
get_formatted_filename(zathura_t* zathura, bool statusbar)
787 788
{
  bool basename_only = false;
Lukas K.'s avatar
Lukas K. committed
789
  const char* file_path = zathura_document_get_uri(zathura->document);
790
  if (file_path == NULL) {
Lukas K.'s avatar
Lukas K. committed
791 792
    file_path = zathura_document_get_path(zathura->document);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
793
  if (statusbar == true) {
794
    girara_setting_get(zathura->ui.session, "statusbar-basename", &basename_only);
Lukas K.'s avatar
Lukas K. committed
795 796
  } else {
    girara_setting_get(zathura->ui.session, "window-title-basename", &basename_only);
797 798
  }

799 800
  if (basename_only == false) {
    bool home_tilde = false;
801 802 803 804 805 806
    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
807
    const size_t file_path_len = file_path ? strlen(file_path) : 0;
808

Sebastian Ramacher's avatar
Sebastian Ramacher committed
809 810 811
    if (home_tilde == true) {
      char* home = girara_get_home_directory(NULL);
      const size_t home_len = home ? strlen(home) : 0;
812

Sidharth Kapur's avatar
Sidharth Kapur committed
813 814 815 816
      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
817
        g_free(home);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
818
        return g_strdup_printf("~%s", &file_path[home_len]);
819
      } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
820
        g_free(home);
821
        return g_strdup(file_path);
822 823
      }
    } else {
824
      return g_strdup(file_path);
825 826
    }
  } else {
827
    const char* basename = zathura_document_get_basename(zathura->document);
828
    return g_strdup(basename);
829 830 831
  }
}

832 833 834 835 836 837
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,
838
                cb_password_dialog, password_dialog_info);
839 840 841
  return FALSE;
}

Moritz Lipp's avatar
Moritz Lipp committed
842
bool
Lukas K.'s avatar
Lukas K. committed
843
document_open(zathura_t* zathura, const char* path, const char* uri, const char* password,
844
              int page_number)
Moritz Lipp's avatar
Moritz Lipp committed
845
{
846
  if (zathura == NULL || zathura->plugins.manager == NULL || path == NULL) {
847
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
848 849
  }

850
  zathura_error_t error = ZATHURA_ERROR_OK;
851
  zathura_document_t* document = zathura_document_open(zathura, path, uri, password, &error);
Moritz Lipp's avatar
Moritz Lipp committed
852

Moritz Lipp's avatar
Moritz Lipp committed
853
  if (document == NULL) {
854
    if (error == ZATHURA_ERROR_INVALID_PASSWORD) {
855
      girara_debug("Invalid or no password.");
856 857 858
      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
859
        password_dialog_info->path = g_strdup(path);
Lukas K.'s avatar
Lukas K. committed
860
        password_dialog_info->uri = g_strdup(uri);
861

Sebastian Ramacher's avatar
Sebastian Ramacher committed
862
        if (password_dialog_info->path != NULL) {
863
          gdk_threads_add_idle(document_open_password_dialog, password_dialog_info);
864 865 866 867 868 869 870
          goto error_out;
        } else {
          free(password_dialog_info);
        }
      }
      goto error_out;
    }
Moritz Lipp's avatar
Moritz Lipp committed
871 872 873
    if (error == ZATHURA_ERROR_OK ) {
      girara_notify(zathura->ui.session, GIRARA_ERROR, _("Unsupported file type. Please install the necessary plugin."));
    }
874
    goto error_out;
Moritz Lipp's avatar
Moritz Lipp committed
875
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
876

877 878
  const char* file_path        = zathura_document_get_path(document);
  unsigned int number_of_pages = zathura_document_get_number_of_pages(document);
879

880 881 882 883 884 885
  if (number_of_pages == 0) {
    girara_notify(zathura->ui.session, GIRARA_WARNING,
        _("Document does not contain any pages"));
    goto error_free;
  }

886 887
  zathura->document = document;

888
  /* read history file */
889 890 891
  zathura_fileinfo_t file_info = {
    .current_page = 0,
    .page_offset = 0,
892
    .zoom = 1,
893 894
    .rotation = 0,
    .pages_per_row = 0,
895
    .first_page_column_list = NULL,
896 897 898
    .position_x = 0,
    .position_y = 0
  };
899 900 901 902
  bool known_file = false;
  if (zathura->database != NULL) {
    known_file = zathura_db_get_fileinfo(zathura->database, file_path, &file_info);
  }
903

Moritz Lipp's avatar
Moritz Lipp committed
904
  /* set page offset */
905
  zathura_document_set_page_offset(document, file_info.page_offset);
906

907 908 909
  /* check for valid zoom value */
  if (file_info.zoom <= DBL_EPSILON) {
    file_info.zoom = 1;
910
  }
911 912
  zathura_document_set_zoom(document,
      zathura_correct_zoom_value(zathura->ui.session, file_info.zoom));
913 914

  /* check current page number */
915 916 917 918 919 920
  /* 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) {
921 922
    girara_warning("document info: '%s' has an invalid page number", file_path);
    zathura_document_set_current_page_number(document, 0);
923
  } else {
924
    zathura_document_set_current_page_number(document, page_number);
925 926 927
  }

  /* check for valid rotation */
928
  if (file_info.rotation % 90 != 0) {
929 930 931
    girara_warning("document info: '%s' has an invalid rotation", file_path);
    zathura_document_set_rotation(document, 0);
  } else {
932
    zathura_document_set_rotation(document, file_info.rotation % 360);
933 934 935 936 937 938 939 940 941 942 943
  }

  /* 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";
944
  if (known_file == false && girara_setting_get(zathura->ui.session, "adjust-open", &(adjust_open)) == true) {
945 946 947 948 949 950 951 952
    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);
953 954
  } else {
    zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_NONE);
955 956
  }

957 958 959 960 961
  /* 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;

962
  /* update statusbar */
Lukas K.'s avatar
Lukas K. committed
963
  char* filename = get_formatted_filename(zathura, true);
964 965
  girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, filename);
  g_free(filename);
966

Moritz Lipp's avatar
Moritz Lipp committed
967 968
  /* install file monitor */
  if (zathura->file_monitor.monitor == NULL) {
969 970 971 972 973 974
    char* filemonitor_backend = NULL;
    girara_setting_get(zathura->ui.session, "filemonitor", &filemonitor_backend);
    zathura_filemonitor_type_t type = ZATHURA_FILEMONITOR_GLIB;
#ifdef G_OS_UNIX
    if (g_strcmp0(filemonitor_backend, "signal") == 0) {
      type = ZATHURA_FILEMONITOR_SIGNAL;
975
    }
976
#endif
Sebastian Ramacher's avatar
Sebastian Ramacher committed
977
    g_free(filemonitor_backend);
Moritz Lipp's avatar
Moritz Lipp committed
978

979 980
    zathura->file_monitor.monitor = zathura_filemonitor_new(file_path, type);
    if (zathura->file_monitor.monitor == NULL) {
981 982
      goto error_free;
    }
983 984
    g_signal_connect(G_OBJECT(zathura->file_monitor.monitor), "reload-file",
                     G_CALLBACK(cb_file_monitor), zathura->ui.session);
985 986 987

    girara_debug("starting file monitor");
    zathura_filemonitor_start(zathura->file_monitor.monitor);
Moritz Lipp's avatar
Moritz Lipp committed
988 989
  }

990
  if (password != NULL) {
991
    g_free(zathura->file_monitor.password);
992
    zathura->file_monitor.password = g_strdup(password);
Moritz Lipp's avatar
Moritz Lipp committed
993 994 995 996 997
    if (zathura->file_monitor.password == NULL) {
      goto error_free;
    }
  }

Moritz Lipp's avatar
Moritz Lipp committed
998 999 1000 1001 1002 1003
  /* create marks list */
  zathura->global.marks = girara_list_new2((girara_free_function_t) mark_free);
  if (zathura->global.marks == NULL) {
    goto error_free;
  }

1004 1005 1006 1007 1008 1009 1010 1011 1012
  /* 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;
  }

1013
  /* threads */
1014
  zathura->sync.render_thread = zathura_renderer_new(cache_size);
1015 1016 1017 1018 1019

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
1020
  /* set up recolor info in ZathuraRenderer */
1021
  char* recolor_dark  = NULL;
1022 1023 1024 1025 1026 1027 1028 1029
  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);

1030 1031 1032 1033 1034
  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);
1035 1036
  girara_setting_get(zathura->ui.session, "recolor-reverse-video", &recolor);
  zathura_renderer_enable_recolor_reverse_video(zathura->sync.render_thread, recolor);
1037

1038 1039 1040 1041 1042 1043
  /* 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));

1044
  const unsigned int view_width = (unsigned int)floor(gtk_adjustment_get_page_size(hadjustment));
1045
  zathura_document_set_viewport_width(zathura->document, view_width);
1046
  const unsigned int view_height = (unsigned int)floor(gtk_adjustment_get_page_size(vadjustment));
1047 1048
  zathura_document_set_viewport_height(zathura->document, view_height);

1049
  zathura_update_view_ppi(zathura);
1050

1051 1052 1053
  /* call screen-changed callback to connect monitors-changed signal on initial screen */
  cb_widget_screen_changed(zathura->ui.session->gtk.view, NULL, zathura);

1054
  /* get initial device scale */
1055 1056
  int device_factor = gtk_widget_get_scale_factor(zathura->ui.session->gtk.view);
  zathura_document_set_device_factors(zathura->document, device_factor, device_factor);
1057

1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
  /* 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;
    }