1 /* SPDX-License-Identifier: Zlib */
2 
3 #include "dbus-interface.h"
4 #include "synctex.h"
5 #include "macros.h"
6 #include "zathura.h"
7 #include "document.h"
8 #include "utils.h"
9 #include "adjustment.h"
10 #include "resources.h"
11 
12 #include <girara/session.h>
13 #include <girara/utils.h>
14 #include <girara/settings.h>
15 #include <gio/gio.h>
16 #include <sys/types.h>
17 #include <string.h>
18 #include <unistd.h>
19 
20 static const char DBUS_XML_FILENAME[] = "/org/pwmt/zathura/DBus/org.pwmt.zathura.xml";
21 
load_xml_data(void)22 static GBytes* load_xml_data(void)
23 {
24   GResource* resource = zathura_resources_get_resource();
25   if (resource != NULL) {
26     return g_resource_lookup_data(resource, DBUS_XML_FILENAME,
27                                   G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
28   }
29 
30   return NULL;
31 }
32 
33 typedef struct private_s {
34   zathura_t* zathura;
35   GDBusNodeInfo* introspection_data;
36   GDBusConnection* connection;
37   guint owner_id;
38   guint registration_id;
39 } ZathuraDbusPrivate;
40 
41 G_DEFINE_TYPE_WITH_CODE(ZathuraDbus, zathura_dbus, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraDbus))
42 
43 /* template for bus name */
44 static const char DBUS_NAME_TEMPLATE[] = "org.pwmt.zathura.PID-%d";
45 /* object path */
46 static const char DBUS_OBJPATH[] = "/org/pwmt/zathura";
47 /* interface name */
48 static const char DBUS_INTERFACE[] = "org.pwmt.zathura";
49 
50 static const GDBusInterfaceVTable interface_vtable;
51 
52 static void
finalize(GObject * object)53 finalize(GObject* object)
54 {
55   ZathuraDbus* dbus        = ZATHURA_DBUS(object);
56   ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
57 
58   if (priv->connection != NULL && priv->registration_id > 0) {
59     g_dbus_connection_unregister_object(priv->connection, priv->registration_id);
60   }
61 
62   if (priv->owner_id > 0) {
63     g_bus_unown_name(priv->owner_id);
64   }
65 
66   if (priv->introspection_data != NULL) {
67     g_dbus_node_info_unref(priv->introspection_data);
68   }
69 
70   G_OBJECT_CLASS(zathura_dbus_parent_class)->finalize(object);
71 }
72 
73 static void
zathura_dbus_class_init(ZathuraDbusClass * class)74 zathura_dbus_class_init(ZathuraDbusClass* class)
75 {
76   /* overwrite methods */
77   GObjectClass* object_class = G_OBJECT_CLASS(class);
78   object_class->finalize     = finalize;
79 }
80 
81 static void
zathura_dbus_init(ZathuraDbus * dbus)82 zathura_dbus_init(ZathuraDbus* dbus)
83 {
84   ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
85   priv->zathura            = NULL;
86   priv->introspection_data = NULL;
87   priv->connection         = NULL;
88   priv->owner_id           = 0;
89   priv->registration_id    = 0;
90 }
91 
92 static void
gdbus_connection_closed(GDBusConnection * UNUSED (connection),gboolean UNUSED (remote_peer_vanished),GError * error,void * UNUSED (data))93 gdbus_connection_closed(GDBusConnection* UNUSED(connection),
94     gboolean UNUSED(remote_peer_vanished), GError* error, void* UNUSED(data))
95 {
96   if (error != NULL) {
97     girara_debug("D-Bus connection closed: %s", error->message);
98   }
99 }
100 
101 static void
bus_acquired(GDBusConnection * connection,const gchar * name,void * data)102 bus_acquired(GDBusConnection* connection, const gchar* name, void* data)
103 {
104   girara_debug("Bus acquired at '%s'.", name);
105 
106   /* register callback for GDBusConnection's closed signal */
107   g_signal_connect(G_OBJECT(connection), "closed",
108                    G_CALLBACK(gdbus_connection_closed), NULL);
109 
110   ZathuraDbus* dbus        = data;
111   ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
112 
113   GError* error = NULL;
114   priv->registration_id = g_dbus_connection_register_object(
115       connection, DBUS_OBJPATH, priv->introspection_data->interfaces[0],
116       &interface_vtable, dbus, NULL, &error);
117   if (priv->registration_id == 0) {
118     girara_warning("Failed to register object on D-Bus connection: %s",
119                    error->message);
120     g_error_free(error);
121     return;
122   }
123 
124   priv->connection = connection;
125 }
126 
127 static void
name_acquired(GDBusConnection * UNUSED (connection),const gchar * name,void * UNUSED (data))128 name_acquired(GDBusConnection* UNUSED(connection), const gchar* name,
129               void* UNUSED(data))
130 {
131   girara_debug("Acquired '%s' on session bus.", name);
132 }
133 
134 static void
name_lost(GDBusConnection * UNUSED (connection),const gchar * name,void * UNUSED (data))135 name_lost(GDBusConnection* UNUSED(connection), const gchar* name,
136           void* UNUSED(data))
137 {
138   girara_debug("Lost connection or failed to acquire '%s' on session bus.",
139                name);
140 }
141 
142 ZathuraDbus*
zathura_dbus_new(zathura_t * zathura)143 zathura_dbus_new(zathura_t* zathura)
144 {
145   GObject* obj = g_object_new(ZATHURA_TYPE_DBUS, NULL);
146   if (obj == NULL) {
147     return NULL;
148   }
149 
150   ZathuraDbus* dbus = ZATHURA_DBUS(obj);
151   ZathuraDbusPrivate* priv   = zathura_dbus_get_instance_private(dbus);
152   priv->zathura     = zathura;
153 
154   GBytes* xml_data = load_xml_data();
155   if (xml_data == NULL)
156   {
157     girara_warning("Failed to load introspection data.");
158     g_object_unref(obj);
159     return NULL;
160   }
161 
162   GError* error = NULL;
163   priv->introspection_data = g_dbus_node_info_new_for_xml((const char*) g_bytes_get_data(xml_data, NULL), &error);
164   g_bytes_unref(xml_data);
165 
166   if (priv->introspection_data == NULL) {
167     girara_warning("Failed to parse introspection data: %s", error->message);
168     g_error_free(error);
169     g_object_unref(obj);
170     return NULL;
171   }
172 
173   char* well_known_name = g_strdup_printf(DBUS_NAME_TEMPLATE, getpid());
174   priv->owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, well_known_name,
175                                   G_BUS_NAME_OWNER_FLAGS_NONE, bus_acquired,
176                                   name_acquired, name_lost, dbus, NULL);
177   g_free(well_known_name);
178 
179   return dbus;
180 }
181 
182 void
zathura_dbus_edit(ZathuraDbus * edit,unsigned int page,unsigned int x,unsigned int y)183 zathura_dbus_edit(ZathuraDbus* edit, unsigned int page, unsigned int x, unsigned int y) {
184   ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(edit);
185 
186   const char* filename = zathura_document_get_path(priv->zathura->document);
187 
188   char* input_file = NULL;
189   unsigned int line = 0;
190   unsigned int column = 0;
191 
192   if (synctex_get_input_line_column(filename, page, x, y, &input_file, &line,
193         &column) == false) {
194     return;
195   }
196 
197   GError* error = NULL;
198   g_dbus_connection_emit_signal(priv->connection, NULL, DBUS_OBJPATH,
199     DBUS_INTERFACE, "Edit", g_variant_new("(suu)", input_file, line, column), &error);
200 
201   g_free(input_file);
202 
203   if (error != NULL) {
204     girara_debug("Failed to emit 'Edit' signal: %s", error->message);
205     g_error_free(error);
206   }
207 }
208 
209 /* D-Bus handler */
210 
211 static void
handle_open_document(zathura_t * zathura,GVariant * parameters,GDBusMethodInvocation * invocation)212 handle_open_document(zathura_t* zathura, GVariant* parameters,
213                      GDBusMethodInvocation* invocation)
214 {
215   gchar* filename = NULL;
216   gchar* password = NULL;
217   gint page = ZATHURA_PAGE_NUMBER_UNSPECIFIED;
218   g_variant_get(parameters, "(ssi)", &filename, &password, &page);
219 
220   document_close(zathura, false);
221   document_open_idle(zathura, filename,
222                      strlen(password) > 0 ? password : NULL,
223                      page,
224                      NULL, NULL);
225   g_free(filename);
226   g_free(password);
227 
228   GVariant* result = g_variant_new("(b)", true);
229   g_dbus_method_invocation_return_value(invocation, result);
230 }
231 
232 static void
handle_close_document(zathura_t * zathura,GVariant * UNUSED (parameters),GDBusMethodInvocation * invocation)233 handle_close_document(zathura_t* zathura, GVariant* UNUSED(parameters),
234                       GDBusMethodInvocation* invocation)
235 {
236   const bool ret = document_close(zathura, false);
237 
238   GVariant* result = g_variant_new("(b)", ret);
239   g_dbus_method_invocation_return_value(invocation, result);
240 }
241 
242 static void
handle_goto_page(zathura_t * zathura,GVariant * parameters,GDBusMethodInvocation * invocation)243 handle_goto_page(zathura_t* zathura, GVariant* parameters,
244                  GDBusMethodInvocation* invocation)
245 {
246   const unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
247 
248   guint page = 0;
249   g_variant_get(parameters, "(u)", &page);
250 
251   bool ret = true;
252   if (page >= number_of_pages) {
253     ret = false;
254   } else {
255     page_set(zathura, page);
256   }
257 
258   GVariant* result = g_variant_new("(b)", ret);
259   g_dbus_method_invocation_return_value(invocation, result);
260 }
261 
262 static void
handle_highlight_rects(zathura_t * zathura,GVariant * parameters,GDBusMethodInvocation * invocation)263 handle_highlight_rects(zathura_t* zathura, GVariant* parameters,
264                        GDBusMethodInvocation* invocation)
265 {
266   const unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
267 
268   guint page = 0;
269   GVariantIter* iter = NULL;
270   GVariantIter* secondary_iter = NULL;
271   g_variant_get(parameters, "(ua(dddd)a(udddd))", &page, &iter,
272                 &secondary_iter);
273 
274   if (page >= number_of_pages) {
275     girara_debug("Got invalid page number.");
276     GVariant* result = g_variant_new("(b)", false);
277     g_variant_iter_free(iter);
278     g_variant_iter_free(secondary_iter);
279     g_dbus_method_invocation_return_value(invocation, result);
280     return;
281   }
282 
283   /* get rectangles */
284   girara_list_t** rectangles = g_try_malloc0(number_of_pages * sizeof(girara_list_t*));
285   if (rectangles == NULL) {
286     g_variant_iter_free(iter);
287     g_variant_iter_free(secondary_iter);
288     g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
289                                           G_DBUS_ERROR_NO_MEMORY,
290                                           "Failed to allocate memory.");
291     return;
292   }
293 
294   rectangles[page] = girara_list_new2(g_free);
295   if (rectangles[page] == NULL) {
296     g_free(rectangles);
297     g_variant_iter_free(iter);
298     g_variant_iter_free(secondary_iter);
299     g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
300                                           G_DBUS_ERROR_NO_MEMORY,
301                                           "Failed to allocate memory.");
302     return;
303   }
304 
305   zathura_rectangle_t temp_rect = { 0, 0, 0, 0 };
306   while (g_variant_iter_loop(iter, "(dddd)", &temp_rect.x1, &temp_rect.x2,
307                              &temp_rect.y1, &temp_rect.y2)) {
308     zathura_rectangle_t* rect = g_try_malloc0(sizeof(zathura_rectangle_t));
309     if (rect == NULL) {
310       g_variant_iter_free(iter);
311       g_variant_iter_free(secondary_iter);
312       girara_list_free(rectangles[page]);
313       g_free(rectangles);
314       g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
315                                             G_DBUS_ERROR_NO_MEMORY,
316                                             "Failed to allocate memory.");
317       return;
318     }
319 
320     *rect = temp_rect;
321     girara_list_append(rectangles[page], rect);
322   }
323   g_variant_iter_free(iter);
324 
325   /* get secondary rectangles */
326   guint temp_page = 0;
327   while (g_variant_iter_loop(secondary_iter, "(udddd)", &temp_page,
328                              &temp_rect.x1, &temp_rect.x2, &temp_rect.y1,
329                              &temp_rect.y2)) {
330     if (temp_page >= number_of_pages) {
331       /* error out here? */
332       girara_debug("Got invalid page number.");
333       continue;
334     }
335 
336     if (rectangles[temp_page] == NULL) {
337       rectangles[temp_page] = girara_list_new2(g_free);
338     }
339 
340     zathura_rectangle_t* rect = g_try_malloc0(sizeof(zathura_rectangle_t));
341     if (rect == NULL || rectangles[temp_page] == NULL) {
342       g_variant_iter_free(secondary_iter);
343       for (unsigned int p = 0; p != number_of_pages; ++p) {
344         girara_list_free(rectangles[p]);
345       }
346       g_free(rectangles);
347       g_free(rect);
348       g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
349                                             G_DBUS_ERROR_NO_MEMORY,
350                                             "Failed to allocate memory.");
351       return;
352     }
353 
354     *rect = temp_rect;
355     girara_list_append(rectangles[temp_page], rect);
356   }
357   g_variant_iter_free(secondary_iter);
358 
359   synctex_highlight_rects(zathura, page, rectangles);
360   g_free(rectangles);
361 
362   GVariant* result = g_variant_new("(b)", true);
363   g_dbus_method_invocation_return_value(invocation, result);
364 }
365 
366 static void
handle_synctex_view(zathura_t * zathura,GVariant * parameters,GDBusMethodInvocation * invocation)367 handle_synctex_view(zathura_t* zathura, GVariant* parameters,
368                     GDBusMethodInvocation* invocation)
369 {
370   gchar* input_file = NULL;
371   guint line = 0;
372   guint column = 0;
373   g_variant_get(parameters, "(suu)", &input_file, &line, &column);
374 
375   const bool ret = synctex_view(zathura, input_file, line, column);
376   g_free(input_file);
377 
378   GVariant* result = g_variant_new("(b)", ret);
379   g_dbus_method_invocation_return_value(invocation, result);
380 }
381 
382 static void
handle_method_call(GDBusConnection * UNUSED (connection),const gchar * UNUSED (sender),const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,void * data)383 handle_method_call(GDBusConnection* UNUSED(connection),
384                    const gchar* UNUSED(sender), const gchar* object_path,
385                    const gchar* interface_name, const gchar* method_name,
386                    GVariant*    parameters, GDBusMethodInvocation* invocation,
387                    void* data)
388 {
389   ZathuraDbus* dbus = data;
390   ZathuraDbusPrivate* priv   = zathura_dbus_get_instance_private(dbus);
391 
392   girara_debug("Handling call '%s.%s' on '%s'.", interface_name, method_name,
393                object_path);
394 
395   static const struct {
396     const char* method;
397     void (*handler)(zathura_t*, GVariant*, GDBusMethodInvocation*);
398     bool needs_document;
399     bool present_window;
400   } handlers[] = {
401     { "OpenDocument", handle_open_document, false, true },
402     { "CloseDocument", handle_close_document, false, false },
403     { "GotoPage", handle_goto_page, true, true },
404     { "HighlightRects", handle_highlight_rects, true, true },
405     { "SynctexView", handle_synctex_view, true, true }
406   };
407 
408   for (size_t idx = 0; idx != sizeof(handlers) / sizeof(handlers[0]); ++idx) {
409     if (g_strcmp0(method_name, handlers[idx].method) != 0) {
410       continue;
411     }
412 
413     if (handlers[idx].needs_document == true && priv->zathura->document == NULL) {
414       g_dbus_method_invocation_return_dbus_error(
415           invocation, "org.pwmt.zathura.NoOpenDocument",
416           "No document has been opened.");
417       return;
418     }
419 
420     (*handlers[idx].handler)(priv->zathura, parameters, invocation);
421 
422     if (handlers[idx].present_window == true && priv->zathura->ui.session->gtk.embed == 0) {
423       gtk_window_present(GTK_WINDOW(priv->zathura->ui.session->gtk.window));
424     }
425 
426     return;
427   }
428 }
429 
430 static GVariant*
handle_get_property(GDBusConnection * UNUSED (connection),const gchar * UNUSED (sender),const gchar * UNUSED (object_path),const gchar * UNUSED (interface_name),const gchar * property_name,GError ** error,void * data)431 handle_get_property(GDBusConnection* UNUSED(connection),
432                     const gchar* UNUSED(sender),
433                     const gchar* UNUSED(object_path),
434                     const gchar* UNUSED(interface_name),
435                     const gchar* property_name, GError** error, void* data)
436 {
437   ZathuraDbus* dbus        = data;
438   ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
439 
440   if (priv->zathura->document == NULL) {
441     g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "No document open.");
442     return NULL;
443   }
444 
445   if (g_strcmp0(property_name, "filename") == 0) {
446     return g_variant_new_string(zathura_document_get_path(priv->zathura->document));
447   } else if (g_strcmp0(property_name, "pagenumber") == 0) {
448     return g_variant_new_uint32(zathura_document_get_current_page_number(priv->zathura->document));
449   } else if (g_strcmp0(property_name, "numberofpages") == 0) {
450     return g_variant_new_uint32(zathura_document_get_number_of_pages(priv->zathura->document));
451   }
452 
453   return NULL;
454 }
455 
456 static const GDBusInterfaceVTable interface_vtable =
457 {
458   .method_call  = handle_method_call,
459   .get_property = handle_get_property,
460   .set_property = NULL
461 };
462 
463 static const unsigned int TIMEOUT = 3000;
464 
465 static bool
call_synctex_view(GDBusConnection * connection,const char * filename,const char * name,const char * input_file,unsigned int line,unsigned int column)466 call_synctex_view(GDBusConnection* connection, const char* filename,
467                   const char* name, const char* input_file, unsigned int line,
468                   unsigned int column)
469 {
470   GError* error       = NULL;
471   GVariant* vfilename = g_dbus_connection_call_sync(
472       connection, name, DBUS_OBJPATH, "org.freedesktop.DBus.Properties", "Get",
473       g_variant_new("(ss)", DBUS_INTERFACE, "filename"), G_VARIANT_TYPE("(v)"),
474       G_DBUS_CALL_FLAGS_NONE, TIMEOUT, NULL, &error);
475   if (vfilename == NULL) {
476     girara_error("Failed to query 'filename' property from '%s': %s",
477                   name, error->message);
478     g_error_free(error);
479     return false;
480   }
481 
482   GVariant* tmp = NULL;
483   g_variant_get(vfilename, "(v)", &tmp);
484   gchar* remote_filename = g_variant_dup_string(tmp, NULL);
485   girara_debug("Filename from '%s': %s", name, remote_filename);
486   g_variant_unref(tmp);
487   g_variant_unref(vfilename);
488 
489   if (g_strcmp0(filename, remote_filename) != 0) {
490     g_free(remote_filename);
491     return false;
492   }
493 
494   g_free(remote_filename);
495 
496   GVariant* ret = g_dbus_connection_call_sync(
497       connection, name, DBUS_OBJPATH, DBUS_INTERFACE, "SynctexView",
498       g_variant_new("(suu)", input_file, line, column),
499       G_VARIANT_TYPE("(b)"), G_DBUS_CALL_FLAGS_NONE, TIMEOUT, NULL, &error);
500   if (ret == NULL) {
501     girara_error("Failed to run SynctexView on '%s': %s", name,
502                  error->message);
503     g_error_free(error);
504     return false;
505   }
506 
507   g_variant_unref(ret);
508   return true;
509 }
510 
511 static int
iterate_instances_call_synctex_view(const char * filename,const char * input_file,unsigned int line,unsigned int column,pid_t hint)512 iterate_instances_call_synctex_view(const char* filename,
513                                     const char* input_file, unsigned int line,
514                                     unsigned int column, pid_t hint)
515 {
516   if (filename == NULL) {
517     return -1;
518   }
519 
520   GError* error = NULL;
521   GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,
522                                                &error);
523   if (connection == NULL) {
524     girara_error("Could not connect to session bus: %s", error->message);
525     g_error_free(error);
526     return -1;
527   }
528 
529   if (hint != -1) {
530     char* well_known_name = g_strdup_printf(DBUS_NAME_TEMPLATE, hint);
531     const bool ret = call_synctex_view(connection, filename, well_known_name,
532                                        input_file, line, column);
533     g_free(well_known_name);
534     return ret ? 1 : -1;
535   }
536 
537   GVariant* vnames = g_dbus_connection_call_sync(
538       connection, "org.freedesktop.DBus", "/org/freedesktop/DBus",
539       "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE("(as)"),
540       G_DBUS_CALL_FLAGS_NONE, TIMEOUT, NULL, &error);
541   if (vnames == NULL) {
542     girara_error("Could not list available names: %s", error->message);
543     g_error_free(error);
544     g_object_unref(connection);
545     return -1;
546   }
547 
548   GVariantIter* iter = NULL;
549   g_variant_get(vnames, "(as)", &iter);
550 
551   gchar* name = NULL;
552   bool found_one = false;
553   while (found_one == false && g_variant_iter_loop(iter, "s", &name) == TRUE) {
554     if (g_str_has_prefix(name, "org.pwmt.zathura.PID") == FALSE) {
555       continue;
556     }
557     girara_debug("Found name: %s", name);
558 
559     found_one = call_synctex_view(connection, filename, name, input_file, line, column);
560   }
561   g_variant_iter_free(iter);
562   g_variant_unref(vnames);
563   g_object_unref(connection);
564 
565   return found_one ? 1 : 0;
566 }
567 
568 int
zathura_dbus_synctex_position(const char * filename,const char * input_file,int line,int column,pid_t hint)569 zathura_dbus_synctex_position(const char* filename, const char* input_file,
570                               int line, int column, pid_t hint)
571 {
572   if (filename == NULL || input_file == NULL || line < 0 || column < 0) {
573     return false;
574   }
575 
576   return iterate_instances_call_synctex_view(filename, input_file, line, column, hint);
577 }
578