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