1 /* SPDX-License-Identifier: Zlib */
2 
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <math.h>
6 #include <string.h>
7 
8 #include <girara/datastructures.h>
9 #include <girara/utils.h>
10 #include <girara/session.h>
11 #include <girara/statusbar.h>
12 #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 #ifdef GDK_WINDOWING_WAYLAND
19 #include <gdk/gdkwayland.h>
20 #endif
21 
22 #ifdef G_OS_UNIX
23 #include <glib-unix.h>
24 #include <gio/gunixinputstream.h>
25 #endif
26 
27 #include "bookmarks.h"
28 #include "callbacks.h"
29 #include "config.h"
30 #ifdef WITH_SQLITE
31 #include "database-sqlite.h"
32 #endif
33 #include "database-plain.h"
34 #include "document.h"
35 #include "shortcuts.h"
36 #include "zathura.h"
37 #include "utils.h"
38 #include "marks.h"
39 #include "render.h"
40 #include "page.h"
41 #include "page-widget.h"
42 #include "plugin.h"
43 #include "adjustment.h"
44 #include "dbus-interface.h"
45 #include "resources.h"
46 #include "synctex.h"
47 #include "content-type.h"
48 #ifdef WITH_SECCOMP
49 #include "seccomp-filters.h"
50 #endif
51 
52 typedef struct zathura_document_info_s {
53   zathura_t* zathura;
54   char* path;
55   char* password;
56   int page_number;
57   char* mode;
58   char* synctex;
59 } zathura_document_info_t;
60 
61 
62 static gboolean document_info_open(gpointer data);
63 
64 #ifdef G_OS_UNIX
65 static gboolean zathura_signal_sigterm(gpointer data);
66 #endif
67 
68 static void
free_document_info(zathura_document_info_t * document_info)69 free_document_info(zathura_document_info_t* document_info)
70 {
71   if (document_info == NULL) {
72     return;
73   }
74 
75   g_free(document_info->path);
76   g_free(document_info->password);
77   g_free(document_info->mode);
78   g_free(document_info->synctex);
79   g_free(document_info);
80 }
81 
82 /* function implementation */
83 zathura_t*
zathura_create(void)84 zathura_create(void)
85 {
86   zathura_t* zathura = g_try_malloc0(sizeof(zathura_t));
87   if (zathura == NULL) {
88     return NULL;
89   }
90 
91   /* global settings */
92   zathura->global.search_direction = FORWARD;
93   zathura->global.sandbox = ZATHURA_SANDBOX_NORMAL;
94 
95   /* plugins */
96   zathura->plugins.manager = zathura_plugin_manager_new();
97   if (zathura->plugins.manager == NULL) {
98     goto error_out;
99   }
100 
101   /* UI */
102   zathura->ui.session = girara_session_create();
103   if (zathura->ui.session == NULL) {
104     goto error_out;
105   }
106 
107   /* set default icon */
108   girara_setting_set(zathura->ui.session, "window-icon", "org.pwmt.zathura");
109 
110 #ifdef G_OS_UNIX
111   /* signal handler */
112   zathura->signals.sigterm = g_unix_signal_add(SIGTERM, zathura_signal_sigterm, zathura);
113 #endif
114 
115   /* MIME type detection */
116   zathura->content_type_context = zathura_content_type_new();
117 
118   zathura->ui.session->global.data = zathura;
119 
120   return zathura;
121 
122 error_out:
123 
124   zathura_free(zathura);
125 
126   return NULL;
127 }
128 
129 static void
create_directories(zathura_t * zathura)130 create_directories(zathura_t* zathura)
131 {
132   static const unsigned int mode = 0700;
133 
134   if (g_mkdir_with_parents(zathura->config.config_dir, mode) == -1) {
135     girara_error("Could not create '%s': %s", zathura->config.config_dir,
136                  strerror(errno));
137   }
138 
139   if (g_mkdir_with_parents(zathura->config.data_dir, mode) == -1) {
140     girara_error("Could not create '%s': %s", zathura->config.data_dir,
141                  strerror(errno));
142   }
143 }
144 
145 void
zathura_update_view_ppi(zathura_t * zathura)146 zathura_update_view_ppi(zathura_t* zathura)
147 {
148   if (zathura == NULL) {
149     return;
150   }
151 
152   /* get view widget GdkMonitor */
153   GdkWindow* window = gtk_widget_get_window(zathura->ui.session->gtk.view); // NULL if not realized
154   if (window == NULL) {
155     return;
156   }
157   GdkDisplay* display = gtk_widget_get_display(zathura->ui.session->gtk.view);
158   if (display == NULL) {
159     return;
160   }
161 
162   double ppi = 0.0;
163 
164   GdkMonitor* monitor = gdk_display_get_monitor_at_window(display, window);
165   if (monitor == NULL) {
166     return;
167   }
168 
169   /* physical width of monitor */
170   const int width_mm = gdk_monitor_get_width_mm(monitor);
171 
172   /* size of monitor in pixels */
173   GdkRectangle monitor_geom;
174   gdk_monitor_get_geometry(monitor, &monitor_geom);
175 
176   /* calculate ppi, knowing that 1 inch = 25.4 mm */
177   if (width_mm == 0) {
178     girara_debug("cannot calculate PPI: monitor has zero width");
179   } else {
180     ppi = monitor_geom.width * 25.4 / width_mm;
181   }
182 
183 #ifdef GDK_WINDOWING_WAYLAND
184   /* work around apparent bug in GDK: on Wayland, monitor geometry doesn't
185    * return values in application pixels as documented, but in device pixels.
186    * */
187   if (GDK_IS_WAYLAND_DISPLAY(display))
188   {
189     /* not using the cached value for the scale factor here to avoid issues
190      * if this function is called before the cached value is updated */
191     const int device_factor = gtk_widget_get_scale_factor(zathura->ui.session->gtk.view);
192     girara_debug("on Wayland, correcting PPI for device scale factor = %d", device_factor);
193     if (device_factor != 0) {
194       ppi /= device_factor;
195     }
196   }
197 #endif
198 
199   const double current_ppi = zathura_document_get_viewport_ppi(zathura->document);
200   if (fabs(ppi - current_ppi) > DBL_EPSILON) {
201     girara_debug("monitor width: %d mm, pixels: %d, ppi: %0.2f", width_mm, monitor_geom.width, ppi);
202     zathura_document_set_viewport_ppi(zathura->document, ppi);
203     render_all(zathura);
204     refresh_view(zathura);
205   }
206 }
207 
208 static bool
init_ui(zathura_t * zathura)209 init_ui(zathura_t* zathura)
210 {
211   if (girara_session_init(zathura->ui.session, "zathura") == false) {
212     girara_error("Failed to initialize girara.");
213     return false;
214   }
215 
216   /* girara events */
217   zathura->ui.session->events.buffer_changed  = cb_buffer_changed;
218   zathura->ui.session->events.unknown_command = cb_unknown_command;
219 
220   /* zathura signals */
221   zathura->signals.refresh_view = g_signal_new(
222     "refresh-view", GTK_TYPE_WIDGET, G_SIGNAL_RUN_LAST, 0, NULL, NULL,
223     g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_POINTER);
224 
225   g_signal_connect(G_OBJECT(zathura->ui.session->gtk.view), "refresh-view",
226                    G_CALLBACK(cb_refresh_view), zathura);
227 
228   g_signal_connect(G_OBJECT(zathura->ui.session->gtk.view),
229       "notify::scale-factor", G_CALLBACK(cb_scale_factor), zathura);
230 
231   g_signal_connect(G_OBJECT(zathura->ui.session->gtk.view),
232       "screen-changed", G_CALLBACK(cb_widget_screen_changed), zathura);
233 
234   g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window),
235       "configure-event", G_CALLBACK(cb_widget_configured), zathura);
236 
237   /* initialize the screen-changed handler to 0 (i.e. invalid) */
238   zathura->signals.monitors_changed_handler = 0;
239 
240   /* page view */
241   zathura->ui.page_widget = gtk_grid_new();
242   gtk_grid_set_row_homogeneous(GTK_GRID(zathura->ui.page_widget), TRUE);
243   gtk_grid_set_column_homogeneous(GTK_GRID(zathura->ui.page_widget), TRUE);
244   if (zathura->ui.page_widget == NULL) {
245     girara_error("Failed to create page widget.");
246     return false;
247   }
248 
249   g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "size-allocate",
250                    G_CALLBACK(cb_view_resized), zathura);
251 
252   GtkAdjustment* hadjustment = gtk_scrolled_window_get_hadjustment(
253                  GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
254 
255   /* Connect hadjustment signals */
256   g_signal_connect(G_OBJECT(hadjustment), "value-changed",
257                    G_CALLBACK(cb_view_hadjustment_value_changed), zathura);
258   g_signal_connect(G_OBJECT(hadjustment), "changed",
259                    G_CALLBACK(cb_view_hadjustment_changed), zathura);
260 
261   GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(
262                  GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
263 
264   /* Connect vadjustment signals */
265   g_signal_connect(G_OBJECT(vadjustment), "value-changed",
266                    G_CALLBACK(cb_view_vadjustment_value_changed), zathura);
267   g_signal_connect(G_OBJECT(vadjustment), "changed",
268                    G_CALLBACK(cb_view_vadjustment_changed), zathura);
269 
270   /* page view alignment */
271   gtk_widget_set_halign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
272   gtk_widget_set_valign(zathura->ui.page_widget, GTK_ALIGN_CENTER);
273 
274   gtk_widget_set_hexpand_set(zathura->ui.page_widget, TRUE);
275   gtk_widget_set_hexpand(zathura->ui.page_widget, FALSE);
276   gtk_widget_set_vexpand_set(zathura->ui.page_widget, TRUE);
277   gtk_widget_set_vexpand(zathura->ui.page_widget, FALSE);
278 
279   gtk_widget_show(zathura->ui.page_widget);
280 
281   /* statusbar */
282   zathura->ui.statusbar.file =
283     girara_statusbar_item_add(zathura->ui.session, TRUE, TRUE, TRUE, NULL);
284   if (zathura->ui.statusbar.file == NULL) {
285     girara_error("Failed to create status bar item.");
286     return false;
287   }
288 
289   zathura->ui.statusbar.buffer =
290     girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
291   if (zathura->ui.statusbar.buffer == NULL) {
292     girara_error("Failed to create status bar item.");
293     return false;
294   }
295 
296   zathura->ui.statusbar.page_number =
297     girara_statusbar_item_add(zathura->ui.session, FALSE, FALSE, FALSE, NULL);
298   if (zathura->ui.statusbar.page_number == NULL) {
299     girara_error("Failed to create status bar item.");
300     return false;
301   }
302 
303   girara_statusbar_item_set_text(zathura->ui.session,
304                                  zathura->ui.statusbar.file, _("[No name]"));
305 
306   /* signals */
307   g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "destroy",
308                    G_CALLBACK(cb_destroy), zathura);
309 
310   return true;
311 }
312 
313 static bool
init_css(zathura_t * zathura)314 init_css(zathura_t* zathura)
315 {
316   GiraraTemplate* csstemplate =
317     girara_session_get_template(zathura->ui.session);
318 
319   static const char index_settings[][16] = {
320     "index-fg",
321     "index-bg",
322     "index-active-fg",
323     "index-active-bg"
324   };
325 
326   for (size_t s = 0; s < LENGTH(index_settings); ++s) {
327     girara_template_add_variable(csstemplate, index_settings[s]);
328 
329     char*   tmp_value = NULL;
330     GdkRGBA rgba      = {0, 0, 0, 0};
331     girara_setting_get(zathura->ui.session, index_settings[s], &tmp_value);
332     if (tmp_value != NULL) {
333       parse_color(&rgba, tmp_value);
334       g_free(tmp_value);
335     }
336 
337     char* color = gdk_rgba_to_string(&rgba);
338     girara_template_set_variable_value(csstemplate, index_settings[s], color);
339     g_free(color);
340   }
341 
342   GResource* css_resource = zathura_resources_get_resource();
343   GBytes* css_data = g_resource_lookup_data(css_resource,
344                                             "/org/pwmt/zathura/CSS/zathura.css_t",
345                                             G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
346   if (css_data != NULL) {
347     char* css = g_strdup_printf("%s\n%s", girara_template_get_base(csstemplate),
348                                 (const char*) g_bytes_get_data(css_data, NULL));
349     girara_template_set_base(csstemplate, css);
350     g_free(css);
351     g_bytes_unref(css_data);
352   } else {
353     return false;
354   }
355 
356   return true;
357 }
358 
359 static void
init_database(zathura_t * zathura)360 init_database(zathura_t* zathura)
361 {
362   char* database = NULL;
363   girara_setting_get(zathura->ui.session, "database", &database);
364 
365   if (g_strcmp0(database, "plain") == 0) {
366     girara_debug("Using plain database backend.");
367     zathura->database = zathura_plaindatabase_new(zathura->config.data_dir);
368 #ifdef WITH_SQLITE
369   } else if (g_strcmp0(database, "sqlite") == 0) {
370     girara_debug("Using sqlite database backend.");
371     char* tmp =
372       g_build_filename(zathura->config.data_dir, "bookmarks.sqlite", NULL);
373     zathura->database = zathura_sqldatabase_new(tmp);
374     g_free(tmp);
375 #endif
376   } else if (g_strcmp0(database, "null") != 0) {
377     girara_error("Database backend '%s' is not supported.", database);
378   }
379 
380   if (zathura->database == NULL && g_strcmp0(database, "null") != 0) {
381     girara_error(
382       "Unable to initialize database. Bookmarks won't be available.");
383   }
384   else {
385     g_object_set(G_OBJECT(zathura->ui.session->command_history), "io",
386                  zathura->database, NULL);
387   }
388   g_free(database);
389 }
390 
391 static void
init_jumplist(zathura_t * zathura)392 init_jumplist(zathura_t* zathura)
393 {
394   int jumplist_size = 20;
395   girara_setting_get(zathura->ui.session, "jumplist-size", &jumplist_size);
396 
397   zathura->jumplist.max_size = jumplist_size < 0 ? 0 : jumplist_size;
398   zathura->jumplist.list     = NULL;
399   zathura->jumplist.size     = 0;
400   zathura->jumplist.cur      = NULL;
401 }
402 
403 static void
init_shortcut_helpers(zathura_t * zathura)404 init_shortcut_helpers(zathura_t* zathura)
405 {
406   zathura->shortcut.mouse.x = 0;
407   zathura->shortcut.mouse.y = 0;
408 
409   zathura->shortcut.toggle_page_mode.pages = 2;
410 
411   zathura->shortcut.toggle_presentation_mode.pages                  = 1;
412   zathura->shortcut.toggle_presentation_mode.first_page_column_list = NULL;
413   zathura->shortcut.toggle_presentation_mode.zoom                   = 1.0;
414 }
415 
416 bool
zathura_init(zathura_t * zathura)417 zathura_init(zathura_t* zathura)
418 {
419   if (zathura == NULL) {
420     return false;
421   }
422 
423   /* Set application ID */
424   g_set_prgname("org.pwmt.zathura");
425 
426   /* create zathura (config/data) directory */
427   create_directories(zathura);
428 
429   /* load plugins */
430   zathura_plugin_manager_load(zathura->plugins.manager);
431 
432   /* configuration */
433   config_load_default(zathura);
434   config_load_files(zathura);
435 
436 #ifdef WITH_SECCOMP
437   /* initialize seccomp filters */
438   switch (zathura->global.sandbox) {
439     case ZATHURA_SANDBOX_NONE:
440       girara_debug("Sandbox deactivated.");
441       break;
442     case ZATHURA_SANDBOX_NORMAL:
443       girara_debug("Basic sandbox allowing normal operation.");
444       if (seccomp_enable_basic_filter() != 0) {
445         girara_error("Failed to initialize basic seccomp filter.");
446         goto error_free;
447       }
448       break;
449     case ZATHURA_SANDBOX_STRICT:
450       girara_debug("Strict sandbox preventing write and network access.");
451       if (seccomp_enable_strict_filter() != 0) {
452         girara_error("Failed to initialize strict seccomp filter.");
453         goto error_free;
454       }
455       /* unset the input method to avoid communication with external services */
456       unsetenv("GTK_IM_MODULE");
457       break;
458   }
459 #endif
460 
461   /* UI */
462   if (init_ui(zathura) == false) {
463     girara_error("Failed to initialize UI.");
464     goto error_free;
465   }
466 
467   /* database */
468   init_database(zathura);
469 
470   /* bookmarks */
471   zathura->bookmarks.bookmarks = girara_sorted_list_new2(
472     (girara_compare_function_t)zathura_bookmarks_compare,
473     (girara_free_function_t)zathura_bookmark_free);
474 
475   /* jumplist */
476   init_jumplist(zathura);
477 
478   /* CSS for index mode */
479   if (init_css(zathura) == false) {
480     girara_error("Failed to initialize CSS.");
481     goto error_free;
482   }
483 
484   /* Shortcut helpers */
485   init_shortcut_helpers(zathura);
486 
487   /* Start D-Bus service */
488   bool dbus = true;
489   girara_setting_get(zathura->ui.session, "dbus-service", &dbus);
490   if (dbus == true) {
491     zathura->dbus = zathura_dbus_new(zathura);
492   }
493 
494   return true;
495 
496 error_free:
497 
498   if (zathura->ui.page_widget != NULL) {
499     g_object_unref(zathura->ui.page_widget);
500   }
501 
502   return false;
503 }
504 
505 void
zathura_free(zathura_t * zathura)506 zathura_free(zathura_t* zathura)
507 {
508   if (zathura == NULL) {
509     return;
510   }
511 
512   document_close(zathura, false);
513 
514   /* MIME type detection */
515   zathura_content_type_free(zathura->content_type_context);
516 
517 #ifdef G_OS_UNIX
518   if (zathura->signals.sigterm > 0) {
519     g_source_remove(zathura->signals.sigterm);
520     zathura->signals.sigterm = 0;
521   }
522 #endif
523 
524   /* stop D-Bus */
525   g_clear_object(&zathura->dbus);
526 
527   if (zathura->ui.session != NULL) {
528     girara_session_destroy(zathura->ui.session);
529   }
530 
531   /* shortcut */
532   if (zathura->shortcut.toggle_presentation_mode.first_page_column_list != NULL) {
533     g_free(zathura->shortcut.toggle_presentation_mode.first_page_column_list);
534   }
535 
536   /* stdin support */
537   if (zathura->stdin_support.file != NULL) {
538     g_unlink(zathura->stdin_support.file);
539     g_free(zathura->stdin_support.file);
540   }
541 
542   /* bookmarks */
543   girara_list_free(zathura->bookmarks.bookmarks);
544 
545   /* database */
546   g_clear_object(&zathura->database);
547 
548   /* free print settings */
549   g_clear_object(&zathura->print.settings);
550   g_clear_object(&zathura->print.page_setup);
551 
552   /* free registered plugins */
553   zathura_plugin_manager_free(zathura->plugins.manager);
554 
555   /* free config variables */
556   g_free(zathura->config.config_dir);
557   g_free(zathura->config.data_dir);
558   g_free(zathura->config.cache_dir);
559 
560   /* free jumplist */
561   if (zathura->jumplist.cur != NULL) {
562     girara_list_iterator_free(zathura->jumplist.cur);
563   }
564 
565   if (zathura->jumplist.list != NULL) {
566     girara_list_free(zathura->jumplist.list);
567   }
568 
569   g_free(zathura);
570 }
571 
572 void
zathura_set_xid(zathura_t * zathura,Window xid)573 zathura_set_xid(zathura_t* zathura, Window xid)
574 {
575   g_return_if_fail(zathura != NULL);
576 
577   zathura->ui.session->gtk.embed = xid;
578 }
579 
580 void
zathura_set_config_dir(zathura_t * zathura,const char * dir)581 zathura_set_config_dir(zathura_t* zathura, const char* dir)
582 {
583   g_return_if_fail(zathura != NULL);
584 
585   if (dir != NULL) {
586     zathura->config.config_dir = g_strdup(dir);
587   } else {
588     gchar* path = girara_get_xdg_path(XDG_CONFIG);
589     zathura->config.config_dir = g_build_filename(path, "zathura", NULL);
590     g_free(path);
591   }
592 }
593 
594 void
zathura_set_data_dir(zathura_t * zathura,const char * dir)595 zathura_set_data_dir(zathura_t* zathura, const char* dir)
596 {
597   g_return_if_fail(zathura != NULL);
598 
599   if (dir != NULL) {
600     zathura->config.data_dir = g_strdup(dir);
601   } else {
602     gchar* path = girara_get_xdg_path(XDG_DATA);
603     zathura->config.data_dir = g_build_filename(path, "zathura", NULL);
604     g_free(path);
605   }
606 }
607 
608 void
zathura_set_cache_dir(zathura_t * zathura,const char * dir)609 zathura_set_cache_dir(zathura_t* zathura, const char* dir)
610 {
611   g_return_if_fail(zathura != NULL);
612 
613   if (dir != NULL) {
614     zathura->config.cache_dir = g_strdup(dir);
615   } else {
616     gchar* path = girara_get_xdg_path(XDG_CACHE);
617     zathura->config.cache_dir = g_build_filename(path, "zathura", NULL);
618     g_free(path);
619   }
620 }
621 
622 static void
add_dir(void * data,void * userdata)623 add_dir(void* data, void* userdata)
624 {
625   const char* path                         = data;
626   zathura_plugin_manager_t* plugin_manager = userdata;
627 
628   zathura_plugin_manager_add_dir(plugin_manager, path);
629 }
630 
631 static void
set_plugin_dir(zathura_t * zathura,const char * dir)632 set_plugin_dir(zathura_t* zathura, const char* dir)
633 {
634   girara_list_t* paths = girara_split_path_array(dir);
635   girara_list_foreach(paths, add_dir, zathura->plugins.manager);
636   girara_list_free(paths);
637 }
638 
639 void
zathura_set_plugin_dir(zathura_t * zathura,const char * dir)640 zathura_set_plugin_dir(zathura_t* zathura, const char* dir)
641 {
642   g_return_if_fail(zathura != NULL);
643   g_return_if_fail(zathura->plugins.manager != NULL);
644 
645   if (dir != NULL) {
646     set_plugin_dir(zathura, dir);
647   } else if (g_getenv("ZATHURA_PLUGINS_PATH") != NULL) {
648     set_plugin_dir(zathura, g_getenv("ZATHURA_PLUGINS_PATH"));
649 #ifdef ZATHURA_PLUGINDIR
650   } else {
651     set_plugin_dir(zathura, ZATHURA_PLUGINDIR);
652 #endif
653   }
654 }
655 
656 void
zathura_set_argv(zathura_t * zathura,char ** argv)657 zathura_set_argv(zathura_t* zathura, char** argv)
658 {
659   g_return_if_fail(zathura != NULL);
660 
661   zathura->global.arguments = argv;
662 }
663 
664 static bool
setup_renderer(zathura_t * zathura,zathura_document_t * document)665 setup_renderer(zathura_t* zathura, zathura_document_t* document)
666 {
667  /* page cache size */
668   int cache_size = 0;
669   girara_setting_get(zathura->ui.session, "page-cache-size", &cache_size);
670   if (cache_size <= 0) {
671     girara_warning("page-cache-size is not positive, using %d instead",
672         ZATHURA_PAGE_CACHE_DEFAULT_SIZE);
673     cache_size = ZATHURA_PAGE_CACHE_DEFAULT_SIZE;
674   }
675 
676   girara_debug("starting renderer with cache size %d", cache_size);
677   ZathuraRenderer* renderer = zathura_renderer_new(cache_size);
678   if (renderer == NULL) {
679     return false;
680   }
681 
682   /* set up recolor info in ZathuraRenderer */
683   char* recolor_dark  = NULL;
684   char* recolor_light = NULL;
685   girara_setting_get(zathura->ui.session, "recolor-darkcolor", &recolor_dark);
686   girara_setting_get(zathura->ui.session, "recolor-lightcolor", &recolor_light);
687   zathura_renderer_set_recolor_colors_str(renderer, recolor_light, recolor_dark);
688   g_free(recolor_dark);
689   g_free(recolor_light);
690 
691   bool recolor = false;
692   girara_setting_get(zathura->ui.session, "recolor", &recolor);
693   zathura_renderer_enable_recolor(renderer, recolor);
694   girara_setting_get(zathura->ui.session, "recolor-keephue", &recolor);
695   zathura_renderer_enable_recolor_hue(renderer, recolor);
696   girara_setting_get(zathura->ui.session, "recolor-reverse-video", &recolor);
697   zathura_renderer_enable_recolor_reverse_video(renderer, recolor);
698 
699   zathura->sync.render_thread = renderer;
700 
701   /* create render request to render window icon */
702   bool window_icon = false;
703   girara_setting_get(zathura->ui.session, "window-icon-document", &window_icon);
704   if (window_icon == true) {
705     girara_debug("starting render request for window icon");
706     ZathuraRenderRequest* request = zathura_render_request_new(renderer, zathura_document_get_page(document, 0));
707     g_signal_connect(request, "completed", G_CALLBACK(cb_window_update_icon), zathura);
708     zathura_render_request_set_render_plain(request, true);
709     zathura_render_request(request, 0);
710     zathura->window_icon_render_request = request;
711   }
712 
713   return true;
714 }
715 
716 #ifdef G_OS_UNIX
717 static gchar*
prepare_document_open_from_stdin(const char * path)718 prepare_document_open_from_stdin(const char* path)
719 {
720   int infileno = -1;
721   if (g_strcmp0(path, "-") == 0) {
722     infileno = fileno(stdin);
723   } else if (g_str_has_prefix(path, "/proc/self/fd/") == true) {
724     char* begin = g_strrstr(path, "/") + 1;
725     gint64 temp = g_ascii_strtoll(begin, NULL, 0);
726     if (temp > INT_MAX || temp < 0) {
727       return NULL;
728     }
729     infileno = (int) temp;
730   } else {
731     return NULL;
732   }
733 
734   if (infileno == -1) {
735     girara_error("Can not read from file descriptor.");
736     return NULL;
737   }
738 
739   GInputStream* input_stream = g_unix_input_stream_new(infileno, false);
740   if (input_stream == NULL) {
741     girara_error("Can not read from file descriptor.");
742     return NULL;
743 
744   }
745 
746   GFileIOStream* iostream = NULL;
747   GError*        error    = NULL;
748   GFile* tmpfile = g_file_new_tmp("zathura.stdin.XXXXXX", &iostream, &error);
749   if (tmpfile == NULL) {
750     if (error != NULL) {
751       girara_error("Can not create temporary file: %s", error->message);
752       g_error_free(error);
753     }
754     g_object_unref(input_stream);
755     return NULL;
756   }
757 
758   const ssize_t count = g_output_stream_splice(
759     g_io_stream_get_output_stream(G_IO_STREAM(iostream)), input_stream,
760     G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);
761   g_object_unref(input_stream);
762   g_object_unref(iostream);
763   if (count == -1) {
764     if (error != NULL) {
765       girara_error("Can not write to temporary file: %s", error->message);
766       g_error_free(error);
767     }
768     g_file_delete(tmpfile, NULL, NULL);
769     g_object_unref(tmpfile);
770     return NULL;
771   }
772 
773   char* file = g_file_get_path(tmpfile);
774   g_object_unref(tmpfile);
775 
776   return file;
777 }
778 #endif
779 
780 static gchar*
prepare_document_open_from_gfile(GFile * source)781 prepare_document_open_from_gfile(GFile* source)
782 {
783   gchar*         file     = NULL;
784   GFileIOStream* iostream = NULL;
785   GError*        error    = NULL;
786 
787   GFile* tmpfile = g_file_new_tmp("zathura.gio.XXXXXX", &iostream, &error);
788   if (tmpfile == NULL) {
789     if (error != NULL) {
790       girara_error("Can not create temporary file: %s", error->message);
791       g_error_free(error);
792     }
793     return NULL;
794   }
795 
796   gboolean rc = g_file_copy(source, tmpfile, G_FILE_COPY_OVERWRITE, NULL, NULL,
797                             NULL, &error);
798   if (rc == FALSE) {
799     if (error != NULL) {
800       girara_error("Can not copy to temporary file: %s", error->message);
801       g_error_free(error);
802     }
803     g_object_unref(iostream);
804     g_object_unref(tmpfile);
805     return NULL;
806   }
807 
808   file = g_file_get_path(tmpfile);
809   g_object_unref(iostream);
810   g_object_unref(tmpfile);
811 
812   return file;
813 }
814 
815 static gboolean
document_info_open(gpointer data)816 document_info_open(gpointer data)
817 {
818   zathura_document_info_t* document_info = data;
819   g_return_val_if_fail(document_info != NULL, FALSE);
820   char* uri = NULL;
821 
822   if (document_info->zathura != NULL && document_info->path != NULL) {
823     char* file = NULL;
824     if (g_strcmp0(document_info->path, "-") == 0 ||
825         g_str_has_prefix(document_info->path, "/proc/self/fd/") == true) {
826 #ifdef G_OS_UNIX
827       file = prepare_document_open_from_stdin(document_info->path);
828 #endif
829       if (file == NULL) {
830         girara_notify(document_info->zathura->ui.session, GIRARA_ERROR,
831                       _("Could not read file from stdin and write it to a temporary file."));
832       } else {
833         document_info->zathura->stdin_support.file = g_strdup(file);
834       }
835     } else {
836       /* expand ~ and ~user in paths if present */
837       char* tmp_path = *document_info->path == '~' ? girara_fix_path(document_info->path) : NULL;
838       GFile* gf = g_file_new_for_commandline_arg(tmp_path != NULL ? tmp_path : document_info->path);
839       g_free(tmp_path);
840 
841       if (g_file_is_native(gf) == TRUE) {
842         /* file was given as a native path */
843         file = g_file_get_path(gf);
844       }
845       else {
846         /* copy file with GIO */
847         uri = g_file_get_uri(gf);
848         file = prepare_document_open_from_gfile(gf);
849         if (file == NULL) {
850           girara_notify(document_info->zathura->ui.session, GIRARA_ERROR,
851                         _("Could not read file from GIO and copy it to a temporary file."));
852         } else {
853           document_info->zathura->stdin_support.file = g_strdup(file);
854         }
855       }
856       g_object_unref(gf);
857     }
858 
859     if (file != NULL) {
860       if (document_info->synctex != NULL) {
861         document_open_synctex(document_info->zathura, file, uri,
862                               document_info->password, document_info->synctex);
863       } else {
864         document_open(document_info->zathura, file, uri, document_info->password,
865                       document_info->page_number);
866       }
867       g_free(file);
868       g_free(uri);
869 
870       if (document_info->mode != NULL) {
871         if (g_strcmp0(document_info->mode, "presentation") == 0) {
872           sc_toggle_presentation(document_info->zathura->ui.session, NULL, NULL,
873                                  0);
874         } else if (g_strcmp0(document_info->mode, "fullscreen") == 0) {
875           sc_toggle_fullscreen(document_info->zathura->ui.session, NULL, NULL,
876                                0);
877         } else {
878           girara_error("Unknown mode: %s", document_info->mode);
879         }
880       }
881     }
882   }
883 
884   free_document_info(document_info);
885   return FALSE;
886 }
887 
888 char*
get_formatted_filename(zathura_t * zathura,bool statusbar)889 get_formatted_filename(zathura_t* zathura, bool statusbar)
890 {
891   bool basename_only = false;
892   const char* file_path = zathura_document_get_uri(zathura->document);
893   if (file_path == NULL) {
894     file_path = zathura_document_get_path(zathura->document);
895   }
896   if (statusbar == true) {
897     girara_setting_get(zathura->ui.session, "statusbar-basename", &basename_only);
898   } else {
899     girara_setting_get(zathura->ui.session, "window-title-basename", &basename_only);
900   }
901 
902   if (basename_only == false) {
903     bool home_tilde = false;
904     if (statusbar) {
905       girara_setting_get(zathura->ui.session, "statusbar-home-tilde", &home_tilde);
906     } else {
907       girara_setting_get(zathura->ui.session, "window-title-home-tilde", &home_tilde);
908     }
909 
910     const size_t file_path_len = file_path ? strlen(file_path) : 0;
911 
912     if (home_tilde == true) {
913       char* home = girara_get_home_directory(NULL);
914       const size_t home_len = home ? strlen(home) : 0;
915 
916       if (home_len > 1
917           && file_path_len >= home_len
918           && g_str_has_prefix(file_path, home)
919           && (!file_path[home_len] || file_path[home_len] == '/')) {
920         g_free(home);
921         return g_strdup_printf("~%s", &file_path[home_len]);
922       } else {
923         g_free(home);
924         return g_strdup(file_path);
925       }
926     } else {
927       return g_strdup(file_path);
928     }
929   } else {
930     const char* basename = zathura_document_get_basename(zathura->document);
931     return g_strdup(basename);
932   }
933 }
934 
935 static gboolean
document_open_password_dialog(gpointer data)936 document_open_password_dialog(gpointer data)
937 {
938   zathura_password_dialog_info_t* password_dialog_info = data;
939 
940   girara_dialog(password_dialog_info->zathura->ui.session, _("Enter password:"), true, NULL,
941                 cb_password_dialog, password_dialog_info);
942   return FALSE;
943 }
944 
945 bool
document_open(zathura_t * zathura,const char * path,const char * uri,const char * password,int page_number)946 document_open(zathura_t* zathura, const char* path, const char* uri, const char* password,
947               int page_number)
948 {
949   if (zathura == NULL || zathura->plugins.manager == NULL || path == NULL) {
950     goto error_out;
951   }
952 
953   /* FIXME: since there are many call chains leading here, check again if we need to expand ~ or
954    * ~user. We should fix all call sites instead */
955   char* tmp_path = *path == '~' ? girara_fix_path(path) : NULL;
956   zathura_error_t error = ZATHURA_ERROR_OK;
957   zathura_document_t* document = zathura_document_open(zathura, tmp_path != NULL ? tmp_path : path, uri, password, &error);
958   g_free(tmp_path);
959 
960   if (document == NULL) {
961     if (error == ZATHURA_ERROR_INVALID_PASSWORD) {
962       girara_debug("Invalid or no password.");
963       zathura_password_dialog_info_t* password_dialog_info = malloc(sizeof(zathura_password_dialog_info_t));
964       if (password_dialog_info != NULL) {
965         password_dialog_info->zathura = zathura;
966         password_dialog_info->path = g_strdup(path);
967         password_dialog_info->uri = g_strdup(uri);
968 
969         if (password_dialog_info->path != NULL) {
970           gdk_threads_add_idle(document_open_password_dialog, password_dialog_info);
971           goto error_out;
972         } else {
973           free(password_dialog_info);
974         }
975       }
976       goto error_out;
977     }
978     if (error == ZATHURA_ERROR_OK ) {
979       girara_notify(zathura->ui.session, GIRARA_ERROR, _("Unsupported file type. Please install the necessary plugin."));
980     }
981     goto error_out;
982   }
983 
984   const char* file_path        = zathura_document_get_path(document);
985   unsigned int number_of_pages = zathura_document_get_number_of_pages(document);
986 
987   if (number_of_pages == 0) {
988     girara_notify(zathura->ui.session, GIRARA_WARNING,
989         _("Document does not contain any pages"));
990     goto error_free;
991   }
992 
993   zathura->document = document;
994 
995   /* read history file */
996   zathura_fileinfo_t file_info = {
997     .current_page = 0,
998     .page_offset = 0,
999     .zoom = 1,
1000     .rotation = 0,
1001     .pages_per_row = 0,
1002     .first_page_column_list = NULL,
1003     .page_right_to_left = false,
1004     .position_x = 0,
1005     .position_y = 0
1006   };
1007   bool known_file = false;
1008   if (zathura->database != NULL) {
1009     const uint8_t* file_hash = zathura_document_get_hash(document);
1010     known_file = zathura_db_get_fileinfo(zathura->database, file_path, file_hash, &file_info);
1011   }
1012 
1013   /* set page offset */
1014   zathura_document_set_page_offset(document, file_info.page_offset);
1015 
1016   /* check for valid zoom value */
1017   if (file_info.zoom <= DBL_EPSILON) {
1018     file_info.zoom = 1;
1019   }
1020   zathura_document_set_zoom(document,
1021       zathura_correct_zoom_value(zathura->ui.session, file_info.zoom));
1022 
1023   /* check current page number */
1024   /* if it wasn't specified on the command-line, get it from file_info */
1025   if (page_number == ZATHURA_PAGE_NUMBER_UNSPECIFIED)
1026     page_number = file_info.current_page;
1027   if (page_number < 0)
1028     page_number += number_of_pages;
1029   if ((unsigned)page_number > number_of_pages) {
1030     girara_warning("document info: '%s' has an invalid page number", file_path);
1031     zathura_document_set_current_page_number(document, 0);
1032   } else {
1033     zathura_document_set_current_page_number(document, page_number);
1034   }
1035 
1036   /* check for valid rotation */
1037   if (file_info.rotation % 90 != 0) {
1038     girara_warning("document info: '%s' has an invalid rotation", file_path);
1039     zathura_document_set_rotation(document, 0);
1040   } else {
1041     zathura_document_set_rotation(document, file_info.rotation % 360);
1042   }
1043 
1044   /* jump to first page if setting enabled */
1045   bool always_first_page = false;
1046   girara_setting_get(zathura->ui.session, "open-first-page", &always_first_page);
1047   if (always_first_page == true) {
1048     zathura_document_set_current_page_number(document, 0);
1049   }
1050 
1051   /* apply open adjustment */
1052   char* adjust_open = "best-fit";
1053   if (known_file == false && girara_setting_get(zathura->ui.session, "adjust-open", &(adjust_open)) == true) {
1054     if (g_strcmp0(adjust_open, "best-fit") == 0) {
1055       zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_BESTFIT);
1056     } else if (g_strcmp0(adjust_open, "width") == 0) {
1057       zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_WIDTH);
1058     } else {
1059       zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_NONE);
1060     }
1061     g_free(adjust_open);
1062   } else {
1063     zathura_document_set_adjust_mode(document, ZATHURA_ADJUST_NONE);
1064   }
1065 
1066   /* initialize bisect state */
1067   zathura->bisect.start = 0;
1068   zathura->bisect.last_jump = zathura_document_get_current_page_number(document);
1069   zathura->bisect.end = number_of_pages - 1;
1070 
1071   /* update statusbar */
1072   char* filename = get_formatted_filename(zathura, true);
1073   girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, filename);
1074   g_free(filename);
1075 
1076   /* install file monitor */
1077   if (zathura->file_monitor.monitor == NULL) {
1078     char* filemonitor_backend = NULL;
1079     girara_setting_get(zathura->ui.session, "filemonitor", &filemonitor_backend);
1080     zathura_filemonitor_type_t type = ZATHURA_FILEMONITOR_GLIB;
1081     if (g_strcmp0(filemonitor_backend, "noop") == 0) {
1082       type = ZATHURA_FILEMONITOR_NOOP;
1083     }
1084 #ifdef G_OS_UNIX
1085     else if (g_strcmp0(filemonitor_backend, "signal") == 0) {
1086       type = ZATHURA_FILEMONITOR_SIGNAL;
1087     }
1088 #endif
1089     g_free(filemonitor_backend);
1090 
1091     zathura->file_monitor.monitor = zathura_filemonitor_new(file_path, type);
1092     if (zathura->file_monitor.monitor == NULL) {
1093       goto error_free;
1094     }
1095     g_signal_connect(G_OBJECT(zathura->file_monitor.monitor), "reload-file",
1096                      G_CALLBACK(cb_file_monitor), zathura->ui.session);
1097 
1098     girara_debug("starting file monitor");
1099     zathura_filemonitor_start(zathura->file_monitor.monitor);
1100   }
1101 
1102   if (password != NULL) {
1103     g_free(zathura->file_monitor.password);
1104     zathura->file_monitor.password = g_strdup(password);
1105     if (zathura->file_monitor.password == NULL) {
1106       goto error_free;
1107     }
1108   }
1109 
1110   /* create marks list */
1111   zathura->global.marks = girara_list_new2((girara_free_function_t) mark_free);
1112   if (zathura->global.marks == NULL) {
1113     goto error_free;
1114   }
1115 
1116   /* threads */
1117   if (!setup_renderer(zathura, document)) {
1118     goto error_free;
1119   }
1120 
1121   /* get view port size */
1122   GtkAdjustment* hadjustment = gtk_scrolled_window_get_hadjustment(
1123                  GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
1124   GtkAdjustment* vadjustment = gtk_scrolled_window_get_vadjustment(
1125                  GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view));
1126 
1127   const unsigned int view_width = (unsigned int)floor(gtk_adjustment_get_page_size(hadjustment));
1128   zathura_document_set_viewport_width(zathura->document, view_width);
1129   const unsigned int view_height = (unsigned int)floor(gtk_adjustment_get_page_size(vadjustment));
1130   zathura_document_set_viewport_height(zathura->document, view_height);
1131 
1132   zathura_update_view_ppi(zathura);
1133 
1134   /* call screen-changed callback to connect monitors-changed signal on initial screen */
1135   cb_widget_screen_changed(zathura->ui.session->gtk.view, NULL, zathura);
1136 
1137   /* get initial device scale */
1138   int device_factor = gtk_widget_get_scale_factor(zathura->ui.session->gtk.view);
1139   zathura_document_set_device_factors(zathura->document, device_factor, device_factor);
1140 
1141   /* create blank pages */
1142   zathura->pages = calloc(number_of_pages, sizeof(GtkWidget*));
1143   if (zathura->pages == NULL) {
1144     goto error_free;
1145   }
1146 
1147   for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
1148     zathura_page_t* page = zathura_document_get_page(document, page_id);
1149     if (page == NULL) {
1150       goto error_free;
1151     }
1152 
1153     GtkWidget* page_widget = zathura_page_widget_new(zathura, page);
1154     if (page_widget == NULL) {
1155       goto error_free;
1156     }
1157 
1158     g_object_ref(page_widget);
1159     zathura->pages[page_id] = page_widget;
1160 
1161     gtk_widget_set_halign(page_widget, GTK_ALIGN_CENTER);
1162     gtk_widget_set_valign(page_widget, GTK_ALIGN_CENTER);
1163 
1164     g_signal_connect(G_OBJECT(page_widget), "text-selected",
1165         G_CALLBACK(cb_page_widget_text_selected), zathura);
1166     g_signal_connect(G_OBJECT(page_widget), "image-selected",
1167         G_CALLBACK(cb_page_widget_image_selected), zathura);
1168     g_signal_connect(G_OBJECT(page_widget), "enter-link",
1169         G_CALLBACK(cb_page_widget_link), (gpointer) true);
1170     g_signal_connect(G_OBJECT(page_widget), "leave-link",
1171         G_CALLBACK(cb_page_widget_link), (gpointer) false);
1172     g_signal_connect(G_OBJECT(page_widget), "scaled-button-release",
1173         G_CALLBACK(cb_page_widget_scaled_button_release), zathura);
1174   }
1175 
1176   /* view mode */
1177   unsigned int pages_per_row = 1;
1178   char* first_page_column_list = NULL;
1179   unsigned int page_padding = 1;
1180   bool page_right_to_left = false;
1181 
1182   girara_setting_get(zathura->ui.session, "page-padding", &page_padding);
1183 
1184   if (file_info.pages_per_row > 0) {
1185     pages_per_row = file_info.pages_per_row;
1186   } else {
1187     girara_setting_get(zathura->ui.session, "pages-per-row", &pages_per_row);
1188   }
1189 
1190   /* read first_page_column list */
1191   if (file_info.first_page_column_list != NULL && *file_info.first_page_column_list != '\0') {
1192     first_page_column_list = file_info.first_page_column_list;
1193     file_info.first_page_column_list = NULL;
1194   } else {
1195     girara_setting_get(zathura->ui.session, "first-page-column", &first_page_column_list);
1196   }
1197 
1198   /* find value for first_page_column */
1199   unsigned int first_page_column = find_first_page_column(first_page_column_list, pages_per_row);
1200 
1201   girara_setting_set(zathura->ui.session, "pages-per-row", &pages_per_row);
1202   girara_setting_set(zathura->ui.session, "first-page-column", first_page_column_list);
1203   g_free(file_info.first_page_column_list);
1204   g_free(first_page_column_list);
1205 
1206   page_right_to_left = file_info.page_right_to_left;
1207 
1208   page_widget_set_mode(zathura, page_padding, pages_per_row, first_page_column, page_right_to_left);
1209   zathura_document_set_page_layout(zathura->document, page_padding, pages_per_row, first_page_column);
1210 
1211   girara_set_view(zathura->ui.session, zathura->ui.page_widget);
1212 
1213   /* bookmarks */
1214   if (zathura->database != NULL) {
1215     if (zathura_bookmarks_load(zathura, file_path) == false) {
1216       girara_debug("Failed to load bookmarks.");
1217     }
1218 
1219     /* jumplist */
1220     if (zathura_jumplist_load(zathura, file_path) == false) {
1221       zathura->jumplist.list = girara_list_new2(g_free);
1222     }
1223   }
1224 
1225   /* update title */
1226   char* formatted_filename = get_formatted_filename(zathura, false);
1227   girara_set_window_title(zathura->ui.session, formatted_filename);
1228   g_free(formatted_filename);
1229 
1230   /* adjust_view */
1231   adjust_view(zathura);
1232   for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
1233     /* set widget size */
1234     zathura_page_t* page = zathura_document_get_page(document, page_id);
1235     unsigned int page_height = 0;
1236     unsigned int page_width  = 0;
1237 
1238     /* adjust_view calls render_all in some cases and render_all calls
1239      * gtk_widget_set_size_request. To be sure that it's really called, do it
1240      * here once again. */
1241     const double height = zathura_page_get_height(page);
1242     const double width = zathura_page_get_width(page);
1243     page_calc_height_width(zathura->document, height, width, &page_height, &page_width, true);
1244     gtk_widget_set_size_request(zathura->pages[page_id], page_width, page_height);
1245 
1246     /* show widget */
1247     gtk_widget_show(zathura->pages[page_id]);
1248   }
1249 
1250   /* Set page */
1251   page_set(zathura, zathura_document_get_current_page_number(document));
1252 
1253   /* Set position (only if restoring from history file) */
1254   if (file_info.current_page == zathura_document_get_current_page_number(document) &&
1255       (file_info.position_x != 0 || file_info.position_y != 0)) {
1256     position_set(zathura, file_info.position_x, file_info.position_y);
1257   }
1258 
1259   update_visible_pages(zathura);
1260 
1261   return true;
1262 
1263 error_free:
1264 
1265   zathura_document_free(document);
1266 
1267 error_out:
1268 
1269   return false;
1270 }
1271 
1272 bool
document_open_synctex(zathura_t * zathura,const char * path,const char * uri,const char * password,const char * synctex)1273 document_open_synctex(zathura_t* zathura, const char* path, const char* uri,
1274                       const char* password, const char* synctex)
1275 {
1276   bool ret = document_open(zathura, path, password, uri,
1277                            ZATHURA_PAGE_NUMBER_UNSPECIFIED);
1278   if (ret == false) {
1279     return false;
1280   }
1281   if (synctex == NULL) {
1282     return true;
1283   }
1284 
1285   int line = 0;
1286   int column = 0;
1287   char* input_file = NULL;
1288   if (synctex_parse_input(synctex, &input_file, &line, &column) == false) {
1289     return false;
1290   }
1291 
1292   ret = synctex_view(zathura, input_file, line, column);
1293   g_free(input_file);
1294   return ret;
1295 }
1296 
1297 void
document_open_idle(zathura_t * zathura,const char * path,const char * password,int page_number,const char * mode,const char * synctex)1298 document_open_idle(zathura_t* zathura, const char* path, const char* password,
1299                    int page_number, const char* mode, const char* synctex)
1300 {
1301   g_return_if_fail(zathura != NULL);
1302   g_return_if_fail(path != NULL);
1303 
1304   zathura_document_info_t* document_info = g_try_malloc0(sizeof(zathura_document_info_t));
1305   if (document_info == NULL) {
1306     return;
1307   }
1308 
1309   document_info->zathura     = zathura;
1310   document_info->path        = g_strdup(path);
1311   if (password != NULL) {
1312     document_info->password  = g_strdup(password);
1313   }
1314   document_info->page_number = page_number;
1315   if (mode != NULL) {
1316     document_info->mode      = g_strdup(mode);
1317   }
1318   if (synctex != NULL) {
1319     document_info->synctex   = g_strdup(synctex);
1320   }
1321 
1322   gdk_threads_add_idle(document_info_open, document_info);
1323 }
1324 
1325 bool
document_save(zathura_t * zathura,const char * path,bool overwrite)1326 document_save(zathura_t* zathura, const char* path, bool overwrite)
1327 {
1328   g_return_val_if_fail(zathura, false);
1329   g_return_val_if_fail(zathura->document, false);
1330   g_return_val_if_fail(path, false);
1331 
1332   gchar* file_path = girara_fix_path(path);
1333   /* use current basename if path points to a directory  */
1334   if (g_file_test(file_path, G_FILE_TEST_IS_DIR) == TRUE) {
1335     char* basename = g_path_get_basename(zathura_document_get_path(zathura->document));
1336     char* tmp = file_path;
1337     file_path = g_build_filename(file_path, basename, NULL);
1338     g_free(tmp);
1339     g_free(basename);
1340   }
1341 
1342   if ((overwrite == false) && g_file_test(file_path, G_FILE_TEST_EXISTS)) {
1343     girara_error("File already exists: %s. Use :write! to overwrite it.", file_path);
1344     g_free(file_path);
1345     return false;
1346   }
1347 
1348   zathura_error_t error = zathura_document_save_as(zathura->document, file_path);
1349   g_free(file_path);
1350 
1351   return (error == ZATHURA_ERROR_OK) ? true : false;
1352 }
1353 
1354 static void
remove_page_from_table(GtkWidget * page,gpointer UNUSED (permanent))1355 remove_page_from_table(GtkWidget* page, gpointer UNUSED(permanent))
1356 {
1357   gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(page)), page);
1358 }
1359 
1360 static void
save_fileinfo_to_db(zathura_t * zathura)1361 save_fileinfo_to_db(zathura_t* zathura)
1362 {
1363   const char* path = zathura_document_get_path(zathura->document);
1364   const uint8_t* file_hash = zathura_document_get_hash(zathura->document);
1365 
1366   zathura_fileinfo_t file_info = {
1367     .current_page = zathura_document_get_current_page_number(zathura->document),
1368     .page_offset = zathura_document_get_page_offset(zathura->document),
1369     .zoom = zathura_document_get_zoom(zathura->document),
1370     .rotation = zathura_document_get_rotation(zathura->document),
1371     .pages_per_row = 1,
1372     .first_page_column_list = "1:2",
1373     .page_right_to_left = false,
1374     .position_x = zathura_document_get_position_x(zathura->document),
1375     .position_y = zathura_document_get_position_y(zathura->document)
1376   };
1377 
1378   girara_setting_get(zathura->ui.session, "pages-per-row",
1379                      &(file_info.pages_per_row));
1380   girara_setting_get(zathura->ui.session, "first-page-column",
1381                      &(file_info.first_page_column_list));
1382   girara_setting_get(zathura->ui.session, "page-right-to-left",
1383                      &(file_info.page_right_to_left));
1384 
1385   /* save file info */
1386   zathura_db_set_fileinfo(zathura->database, path, file_hash, &file_info);
1387   /* save jumplist */
1388   zathura_db_save_jumplist(zathura->database, path, zathura->jumplist.list);
1389 
1390   g_free(file_info.first_page_column_list);
1391 }
1392 
1393 bool
document_close(zathura_t * zathura,bool keep_monitor)1394 document_close(zathura_t* zathura, bool keep_monitor)
1395 {
1396   if (zathura == NULL || zathura->document == NULL) {
1397     return false;
1398   }
1399 
1400   /* reset window icon */
1401   if (zathura->ui.session != NULL && zathura->window_icon_render_request != NULL) {
1402     char* window_icon = NULL;
1403     girara_setting_get(zathura->ui.session, "window-icon", &window_icon);
1404     girara_setting_set(zathura->ui.session, "window-icon", window_icon);
1405     g_free(window_icon);
1406   }
1407 
1408   /* stop rendering */
1409   zathura_renderer_stop(zathura->sync.render_thread);
1410   g_clear_object(&zathura->window_icon_render_request);
1411 
1412   /* remove monitor */
1413   if (keep_monitor == false) {
1414     g_clear_object(&zathura->file_monitor.monitor);
1415 
1416     if (zathura->file_monitor.password != NULL) {
1417       g_free(zathura->file_monitor.password);
1418       zathura->file_monitor.password = NULL;
1419     }
1420   }
1421 
1422   /* remove marks */
1423   if (zathura->global.marks != NULL) {
1424     girara_list_free(zathura->global.marks);
1425     zathura->global.marks = NULL;
1426   }
1427 
1428   /* store file information */
1429   if (zathura->database != NULL) {
1430     save_fileinfo_to_db(zathura);
1431   }
1432 
1433   /* remove jump list */
1434   girara_list_iterator_free(zathura->jumplist.cur);
1435   zathura->jumplist.cur = NULL;
1436   girara_list_free(zathura->jumplist.list);
1437   zathura->jumplist.list = NULL;
1438   zathura->jumplist.size = 0;
1439 
1440   /* release render thread */
1441   g_clear_object(&zathura->sync.render_thread);
1442 
1443   /* remove widgets */
1444   gtk_container_foreach(GTK_CONTAINER(zathura->ui.page_widget), remove_page_from_table, NULL);
1445   for (unsigned int i = 0; i < zathura_document_get_number_of_pages(zathura->document); i++) {
1446     g_object_unref(zathura->pages[i]);
1447   }
1448   free(zathura->pages);
1449   zathura->pages = NULL;
1450 
1451   /* remove document */
1452   zathura_document_free(zathura->document);
1453   zathura->document = NULL;
1454 
1455   /* remove index */
1456   if (zathura->ui.index != NULL) {
1457     g_object_ref_sink(zathura->ui.index);
1458     zathura->ui.index = NULL;
1459   }
1460 
1461   gtk_widget_hide(zathura->ui.page_widget);
1462 
1463   statusbar_page_number_update(zathura);
1464 
1465   if (zathura->ui.session != NULL && zathura->ui.statusbar.file != NULL) {
1466     girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, _("[No name]"));
1467   }
1468 
1469   /* update title */
1470   girara_set_window_title(zathura->ui.session, "zathura");
1471 
1472   return true;
1473 }
1474 
1475 bool
page_set(zathura_t * zathura,unsigned int page_id)1476 page_set(zathura_t* zathura, unsigned int page_id)
1477 {
1478   if (zathura == NULL || zathura->document == NULL) {
1479     goto error_out;
1480   }
1481 
1482   zathura_page_t* page = zathura_document_get_page(zathura->document, page_id);
1483   if (page == NULL) {
1484     goto error_out;
1485   }
1486 
1487   zathura_document_set_current_page_number(zathura->document, page_id);
1488 
1489   bool continuous_hist_save = false;
1490   girara_setting_get(zathura->ui.session, "continuous-hist-save", &continuous_hist_save);
1491   if (continuous_hist_save) {
1492     save_fileinfo_to_db(zathura);
1493   }
1494 
1495   /* negative position means auto */
1496   return position_set(zathura, -1, -1);
1497 
1498 error_out:
1499   return false;
1500 }
1501 
1502 void
statusbar_page_number_update(zathura_t * zathura)1503 statusbar_page_number_update(zathura_t* zathura)
1504 {
1505   if (zathura == NULL || zathura->ui.statusbar.page_number == NULL) {
1506     return;
1507   }
1508 
1509   unsigned int number_of_pages     = zathura_document_get_number_of_pages(zathura->document);
1510   unsigned int current_page_number = zathura_document_get_current_page_number(zathura->document);
1511 
1512   if (zathura->document != NULL) {
1513     zathura_page_t* page = zathura_document_get_page(zathura->document, current_page_number);
1514     char* page_label = zathura_page_get_label(page, NULL);
1515 
1516     char* page_number_text = NULL;
1517     if (page_label != NULL) {
1518       page_number_text = g_strdup_printf("[%s (%d/%d)]", page_label, current_page_number + 1, number_of_pages);
1519       g_free(page_label);
1520     } else {
1521       page_number_text = g_strdup_printf("[%d/%d]", current_page_number + 1, number_of_pages);
1522     }
1523     girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.page_number, page_number_text);
1524 
1525     bool page_number_in_window_title = false;
1526     girara_setting_get(zathura->ui.session, "window-title-page", &page_number_in_window_title);
1527 
1528     if (page_number_in_window_title == true) {
1529       char* filename = get_formatted_filename(zathura, false);
1530       char* title = g_strdup_printf("%s %s", filename, page_number_text);
1531       girara_set_window_title(zathura->ui.session, title);
1532       g_free(title);
1533       g_free(filename);
1534     }
1535 
1536     g_free(page_number_text);
1537   } else {
1538     girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.page_number, "");
1539   }
1540 }
1541 
1542 void
page_widget_set_mode(zathura_t * zathura,unsigned int page_padding,unsigned int pages_per_row,unsigned int first_page_column,bool page_right_to_left)1543 page_widget_set_mode(zathura_t* zathura, unsigned int page_padding,
1544                      unsigned int pages_per_row, unsigned int first_page_column,
1545                      bool page_right_to_left)
1546 {
1547   /* show at least one page */
1548   if (pages_per_row == 0) {
1549     pages_per_row = 1;
1550   }
1551 
1552   /* ensure: 0 < first_page_column <= pages_per_row */
1553   if (first_page_column < 1) {
1554     first_page_column = 1;
1555   }
1556   if (first_page_column > pages_per_row) {
1557     first_page_column = ((first_page_column - 1) % pages_per_row) + 1;
1558   }
1559 
1560   if (zathura->document == NULL) {
1561     return;
1562   }
1563 
1564   gtk_container_foreach(GTK_CONTAINER(zathura->ui.page_widget), remove_page_from_table, NULL);
1565 
1566   unsigned int number_of_pages     = zathura_document_get_number_of_pages(zathura->document);
1567 
1568   gtk_grid_set_row_spacing(GTK_GRID(zathura->ui.page_widget), page_padding);
1569   gtk_grid_set_column_spacing(GTK_GRID(zathura->ui.page_widget), page_padding);
1570 
1571   for (unsigned int i = 0; i < number_of_pages; i++) {
1572     int x = (i + first_page_column - 1) % pages_per_row;
1573     int y = (i + first_page_column - 1) / pages_per_row;
1574 
1575     GtkWidget* page_widget = zathura->pages[i];
1576     if (page_right_to_left) {
1577       x = pages_per_row - 1 - x;
1578     }
1579 
1580     gtk_grid_attach(GTK_GRID(zathura->ui.page_widget), page_widget, x, y, 1, 1);
1581   }
1582 
1583   gtk_widget_show_all(zathura->ui.page_widget);
1584 }
1585 
1586 bool
position_set(zathura_t * zathura,double position_x,double position_y)1587 position_set(zathura_t* zathura, double position_x, double position_y)
1588 {
1589   if (zathura == NULL || zathura->document == NULL) {
1590     return false;
1591   }
1592 
1593   double comppos_x, comppos_y;
1594   const unsigned int page_id = zathura_document_get_current_page_number(zathura->document);
1595 
1596   bool vertical_center = false;
1597   girara_setting_get(zathura->ui.session, "vertical-center", &vertical_center);
1598 
1599   /* xalign = 0.5: center horizontally (with the page, not the document) */
1600   if (vertical_center == true) {
1601     /* yalign = 0.5: center vertically */
1602     page_number_to_position(zathura->document, page_id, 0.5, 0.5, &comppos_x, &comppos_y);
1603   } else {
1604     /* yalign = 0.0: align page an viewport edges at the top               */
1605     page_number_to_position(zathura->document, page_id, 0.5, 0.0, &comppos_x, &comppos_y);
1606   }
1607 
1608   /* automatic horizontal adjustment */
1609   zathura_adjust_mode_t adjust_mode = zathura_document_get_adjust_mode(zathura->document);
1610 
1611   /* negative position_x mean: use the computed value */
1612   if (position_x < 0) {
1613     position_x = comppos_x;
1614     bool zoom_center = false;
1615     girara_setting_get(zathura->ui.session, "zoom-center", &zoom_center);
1616 
1617     /* center horizontally */
1618     if (adjust_mode == ZATHURA_ADJUST_BESTFIT ||
1619       adjust_mode == ZATHURA_ADJUST_WIDTH ||
1620       zoom_center == true) {
1621         position_x = 0.5;
1622     }
1623   }
1624 
1625   if (position_y < 0) {
1626     position_y = comppos_y;
1627   }
1628 
1629   /* set the position */
1630   zathura_document_set_position_x(zathura->document, position_x);
1631   zathura_document_set_position_y(zathura->document, position_y);
1632 
1633   /* trigger a 'change' event for both adjustments */
1634   refresh_view(zathura);
1635 
1636   return true;
1637 }
1638 
1639 void
refresh_view(zathura_t * zathura)1640 refresh_view(zathura_t* zathura)
1641 {
1642   g_return_if_fail(zathura != NULL);
1643 
1644   /* emit a custom refresh-view signal */
1645   g_signal_emit(zathura->ui.session->gtk.view, zathura->signals.refresh_view,
1646                 0, zathura);
1647 }
1648 
1649 bool
adjust_view(zathura_t * zathura)1650 adjust_view(zathura_t* zathura)
1651 {
1652   g_return_val_if_fail(zathura != NULL, false);
1653 
1654   if (zathura->ui.page_widget == NULL || zathura->document == NULL) {
1655     goto error_ret;
1656   }
1657 
1658   zathura_adjust_mode_t adjust_mode = zathura_document_get_adjust_mode(zathura->document);
1659   if (adjust_mode == ZATHURA_ADJUST_NONE) {
1660     /* there is nothing todo */
1661     goto error_ret;
1662   }
1663 
1664   unsigned int cell_height = 0, cell_width = 0;
1665   unsigned int document_height = 0, document_width = 0;
1666   unsigned int view_height = 0, view_width = 0;
1667 
1668   zathura_document_get_cell_size(zathura->document, &cell_height, &cell_width);
1669   zathura_document_get_document_size(zathura->document, &document_height, &document_width);
1670   zathura_document_get_viewport_size(zathura->document, &view_height, &view_width);
1671 
1672   if (view_height == 0 || view_width == 0 || cell_height == 0 || cell_width == 0) {
1673     goto error_ret;
1674   }
1675 
1676   double page_ratio = (double)cell_height / (double)document_width;
1677   double view_ratio = (double)view_height / (double)view_width;
1678   double zoom = zathura_document_get_zoom(zathura->document);
1679   double newzoom = zoom;
1680 
1681   if (adjust_mode == ZATHURA_ADJUST_WIDTH ||
1682       (adjust_mode == ZATHURA_ADJUST_BESTFIT && page_ratio < view_ratio)) {
1683     newzoom *= (double)view_width / (double)document_width;
1684   } else if (adjust_mode == ZATHURA_ADJUST_BESTFIT) {
1685     newzoom *= (double)view_height / (double)cell_height;
1686   } else {
1687     goto error_ret;
1688   }
1689 
1690   /* save new zoom and recompute cell size */
1691   zathura_document_set_zoom(zathura->document, newzoom);
1692   unsigned int new_cell_height = 0, new_cell_width = 0;
1693   zathura_document_get_cell_size(zathura->document, &new_cell_height, &new_cell_width);
1694 
1695   /* if the change in zoom changes page cell dimensions by at least one pixel, render */
1696   if (abs((int)new_cell_width - (int)cell_width) > 1 ||
1697       abs((int)new_cell_height - (int)cell_height) > 1) {
1698     render_all(zathura);
1699     refresh_view(zathura);
1700   } else {
1701     /* otherwise set the old zoom and leave */
1702     zathura_document_set_zoom(zathura->document, zoom);
1703   }
1704 
1705 error_ret:
1706   return false;
1707 }
1708 
1709 #ifdef G_OS_UNIX
1710 static gboolean
zathura_signal_sigterm(gpointer data)1711 zathura_signal_sigterm(gpointer data)
1712 {
1713   if (data != NULL) {
1714     zathura_t* zathura = data;
1715     cb_destroy(NULL, zathura);
1716   }
1717 
1718   return TRUE;
1719 }
1720 #endif
1721