dbus-interface.c 17.3 KB
Newer Older
1 2
/* See LICENSE file for license and copyright information */

Sebastian Ramacher's avatar
Sebastian Ramacher committed
3
#include "dbus-interface.h"
4 5 6 7
#include "synctex.h"
#include "macros.h"
#include "zathura.h"
#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>
Sebastian Ramacher's avatar
Sebastian Ramacher committed
14
#include <girara/settings.h>
15 16
#include <gio/gio.h>
#include <sys/types.h>
17
#include <string.h>
18 19
#include <unistd.h>

20
static const char DBUS_XML_FILENAME[] = "/org/pwmt/zathura/DBus/org.pwmt.zathura.xml";
21

22
static GBytes* load_xml_data(void)
23
{
24 25 26 27
  GResource* resource = zathura_resources_get_resource();
  if (resource != NULL) {
    return g_resource_lookup_data(resource, DBUS_XML_FILENAME,
                                  G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
28 29 30 31 32
  }

  return NULL;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
33
G_DEFINE_TYPE(ZathuraDbus, zathura_dbus, G_TYPE_OBJECT)
34 35 36

/* template for bus name */
static const char DBUS_NAME_TEMPLATE[] = "org.pwmt.zathura.PID-%d";
37
/* object path */
38
static const char DBUS_OBJPATH[] = "/org/pwmt/zathura";
39
/* interface name */
40
static const char DBUS_INTERFACE[] = "org.pwmt.zathura";
41 42 43 44 45 46 47 48 49 50

typedef struct private_s {
  zathura_t* zathura;
  GDBusNodeInfo* introspection_data;
  GDBusConnection* connection;
  guint owner_id;
  guint registration_id;
} private_t;

#define GET_PRIVATE(obj) \
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
51
  (G_TYPE_INSTANCE_GET_PRIVATE((obj), ZATHURA_TYPE_DBUS, private_t))
52 53 54 55 56 57

static const GDBusInterfaceVTable interface_vtable;

static void
finalize(GObject* object)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
58 59
  ZathuraDbus* dbus = ZATHURA_DBUS(object);
  private_t* priv   = GET_PRIVATE(dbus);
60 61 62 63 64 65 66 67 68 69 70 71 72

  if (priv->connection != NULL && priv->registration_id > 0) {
    g_dbus_connection_unregister_object(priv->connection, priv->registration_id);
  }

  if (priv->owner_id > 0) {
    g_bus_unown_name(priv->owner_id);
  }

  if (priv->introspection_data != NULL) {
    g_dbus_node_info_unref(priv->introspection_data);
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
73
  G_OBJECT_CLASS(zathura_dbus_parent_class)->finalize(object);
74 75 76
}

static void
Sebastian Ramacher's avatar
Sebastian Ramacher committed
77
zathura_dbus_class_init(ZathuraDbusClass* class)
78 79 80 81 82 83 84 85 86 87
{
  /* add private members */
  g_type_class_add_private(class, sizeof(private_t));

  /* overwrite methods */
  GObjectClass* object_class = G_OBJECT_CLASS(class);
  object_class->finalize     = finalize;
}

static void
Sebastian Ramacher's avatar
Sebastian Ramacher committed
88
zathura_dbus_init(ZathuraDbus* dbus)
89
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
90
  private_t* priv          = GET_PRIVATE(dbus);
91 92 93 94 95 96 97
  priv->zathura            = NULL;
  priv->introspection_data = NULL;
  priv->connection         = NULL;
  priv->owner_id           = 0;
  priv->registration_id    = 0;
}

98 99 100 101 102 103 104 105 106
static void
gdbus_connection_closed(GDBusConnection* UNUSED(connection),
    gboolean UNUSED(remote_peer_vanished), GError* error, void* UNUSED(data))
{
  if (error != NULL) {
    girara_debug("D-Bus connection closed: %s", error->message);
  }
}

107 108 109 110 111
static void
bus_acquired(GDBusConnection* connection, const gchar* name, void* data)
{
  girara_debug("Bus acquired at '%s'.", name);

112 113 114 115
  /* register callback for GDBusConnection's closed signal */
  g_signal_connect(G_OBJECT(connection), "closed",
                   G_CALLBACK(gdbus_connection_closed), NULL);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
116 117
  ZathuraDbus* dbus = data;
  private_t* priv   = GET_PRIVATE(dbus);
118 119

  GError* error = NULL;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
120 121
  priv->registration_id = g_dbus_connection_register_object(
      connection, DBUS_OBJPATH, priv->introspection_data->interfaces[0],
122 123 124
      &interface_vtable, dbus, NULL, &error);
  if (priv->registration_id == 0) {
    girara_warning("Failed to register object on D-Bus connection: %s",
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
125
                   error->message);
126 127 128 129 130 131 132 133 134
    g_error_free(error);
    return;
  }

  priv->connection = connection;
}

static void
name_acquired(GDBusConnection* UNUSED(connection), const gchar* name,
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
135
              void* UNUSED(data))
136 137 138 139 140 141
{
  girara_debug("Acquired '%s' on session bus.", name);
}

static void
name_lost(GDBusConnection* UNUSED(connection), const gchar* name,
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
142
          void* UNUSED(data))
143 144
{
  girara_debug("Lost connection or failed to acquire '%s' on session bus.",
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
145
               name);
146 147
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
148 149
ZathuraDbus*
zathura_dbus_new(zathura_t* zathura)
150
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
151
  GObject* obj = g_object_new(ZATHURA_TYPE_DBUS, NULL);
152 153 154 155
  if (obj == NULL) {
    return NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
156 157 158
  ZathuraDbus* dbus = ZATHURA_DBUS(obj);
  private_t* priv   = GET_PRIVATE(dbus);
  priv->zathura     = zathura;
159

160
  GBytes* xml_data = load_xml_data();
161 162 163 164 165 166 167
  if (xml_data == NULL)
  {
    girara_warning("Failed to load introspection data.");
    g_object_unref(obj);
    return NULL;
  }

168
  GError* error = NULL;
169 170
  priv->introspection_data = g_dbus_node_info_new_for_xml((const char*) g_bytes_get_data(xml_data, NULL), &error);
  g_bytes_unref(xml_data);
171

172 173 174 175 176 177 178 179
  if (priv->introspection_data == NULL) {
    girara_warning("Failed to parse introspection data: %s", error->message);
    g_error_free(error);
    g_object_unref(obj);
    return NULL;
  }

  char* well_known_name = g_strdup_printf(DBUS_NAME_TEMPLATE, getpid());
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
180 181 182
  priv->owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, well_known_name,
                                  G_BUS_NAME_OWNER_FLAGS_NONE, bus_acquired,
                                  name_acquired, name_lost, dbus, NULL);
183 184
  g_free(well_known_name);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
185
  return dbus;
186 187
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
void
zathura_dbus_edit(ZathuraDbus* edit, unsigned int page, unsigned int x, unsigned int y) {
  private_t* priv = GET_PRIVATE(edit);

  const char* filename = zathura_document_get_path(priv->zathura->document);

  char* input_file = NULL;
  unsigned int line = 0;
  unsigned int column = 0;

  if (synctex_get_input_line_column(filename, page, x, y, &input_file, &line,
        &column) == false) {
    return;
  }

  GError* error = NULL;
  g_dbus_connection_emit_signal(priv->connection, NULL, DBUS_OBJPATH,
    DBUS_INTERFACE, "Edit", g_variant_new("(suu)", input_file, x, y), &error);

  g_free(input_file);

  if (error != NULL) {
    girara_debug("Failed to emit 'Edit' signal: %s", error->message);
    g_error_free(error);
  }
}

215 216 217
/* D-Bus handler */

static void
218 219
handle_open_document(zathura_t* zathura, GVariant* parameters,
                     GDBusMethodInvocation* invocation)
220
{
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
  gchar* filename = NULL;
  gchar* password = NULL;
  gint page = ZATHURA_PAGE_NUMBER_UNSPECIFIED;
  g_variant_get(parameters, "(ssi)", &filename, &password, &page);

  document_close(zathura, false);
  document_open_idle(zathura, filename,
                     strlen(password) > 0 ? password : NULL,
                     page,
                     NULL, NULL);
  g_free(filename);
  g_free(password);

  GVariant* result = g_variant_new("(b)", true);
  g_dbus_method_invocation_return_value(invocation, result);
}
237

238 239 240 241 242
static void
handle_close_document(zathura_t* zathura, GVariant* UNUSED(parameters),
                      GDBusMethodInvocation* invocation)
{
  const bool ret = document_close(zathura, false);
243

244 245 246
  GVariant* result = g_variant_new("(b)", ret);
  g_dbus_method_invocation_return_value(invocation, result);
}
247

248 249 250 251 252
static void
handle_goto_page(zathura_t* zathura, GVariant* parameters,
                 GDBusMethodInvocation* invocation)
{
  const unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
253

254 255 256 257 258 259 260 261
  guint page = 0;
  g_variant_get(parameters, "(u)", &page);

  bool ret = true;
  if (page >= number_of_pages) {
    ret = false;
  } else {
    page_set(zathura, page);
262 263
  }

264 265 266
  GVariant* result = g_variant_new("(b)", ret);
  g_dbus_method_invocation_return_value(invocation, result);
}
267

268 269 270 271 272
static void
handle_highlight_rects(zathura_t* zathura, GVariant* parameters,
                       GDBusMethodInvocation* invocation)
{
  const unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
273

274 275 276 277 278
  guint page = 0;
  GVariantIter* iter = NULL;
  GVariantIter* secondary_iter = NULL;
  g_variant_get(parameters, "(ua(dddd)a(udddd))", &page, &iter,
                &secondary_iter);
279

280 281 282 283 284
  if (page >= number_of_pages) {
    girara_debug("Got invalid page number.");
    GVariant* result = g_variant_new("(b)", false);
    g_variant_iter_free(iter);
    g_variant_iter_free(secondary_iter);
285
    g_dbus_method_invocation_return_value(invocation, result);
286 287 288 289 290 291 292 293 294 295 296 297 298
    return;
  }

  /* get rectangles */
  girara_list_t** rectangles = g_try_malloc0(number_of_pages * sizeof(girara_list_t*));
  if (rectangles == NULL) {
    g_variant_iter_free(iter);
    g_variant_iter_free(secondary_iter);
    g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                          G_DBUS_ERROR_NO_MEMORY,
                                          "Failed to allocate memory.");
    return;
  }
299

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
  rectangles[page] = girara_list_new2(g_free);
  if (rectangles[page] == NULL) {
    g_free(rectangles);
    g_variant_iter_free(iter);
    g_variant_iter_free(secondary_iter);
    g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                          G_DBUS_ERROR_NO_MEMORY,
                                          "Failed to allocate memory.");
    return;
  }

  zathura_rectangle_t temp_rect = { 0, 0, 0, 0 };
  while (g_variant_iter_loop(iter, "(dddd)", &temp_rect.x1, &temp_rect.x2,
                             &temp_rect.y1, &temp_rect.y2)) {
    zathura_rectangle_t* rect = g_try_malloc0(sizeof(zathura_rectangle_t));
    if (rect == NULL) {
316 317
      g_variant_iter_free(iter);
      g_variant_iter_free(secondary_iter);
318 319
      girara_list_free(rectangles[page]);
      g_free(rectangles);
320 321 322
      g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                            G_DBUS_ERROR_NO_MEMORY,
                                            "Failed to allocate memory.");
323 324 325
      return;
    }

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    *rect = temp_rect;
    girara_list_append(rectangles[page], rect);
  }
  g_variant_iter_free(iter);

  /* get secondary rectangles */
  guint temp_page = 0;
  while (g_variant_iter_loop(secondary_iter, "(udddd)", &temp_page,
                             &temp_rect.x1, &temp_rect.x2, &temp_rect.y1,
                             &temp_rect.y2)) {
    if (temp_page >= number_of_pages) {
      /* error out here? */
      girara_debug("Got invalid page number.");
      continue;
    }

    if (rectangles[temp_page] == NULL) {
      rectangles[temp_page] = girara_list_new2(g_free);
    }

    zathura_rectangle_t* rect = g_try_malloc0(sizeof(zathura_rectangle_t));
    if (rect == NULL || rectangles[temp_page] == NULL) {
348
      g_variant_iter_free(secondary_iter);
349 350 351 352 353
      for (unsigned int p = 0; p != number_of_pages; ++p) {
        girara_list_free(rectangles[p]);
      }
      g_free(rectangles);
      g_free(rect);
354 355 356 357 358
      g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                            G_DBUS_ERROR_NO_MEMORY,
                                            "Failed to allocate memory.");
      return;
    }
359

360 361 362 363
    *rect = temp_rect;
    girara_list_append(rectangles[temp_page], rect);
  }
  g_variant_iter_free(secondary_iter);
364

365 366
  synctex_highlight_rects(zathura, page, rectangles);
  g_free(rectangles);
367

368 369 370
  GVariant* result = g_variant_new("(b)", true);
  g_dbus_method_invocation_return_value(invocation, result);
}
371

372 373 374 375 376 377 378 379
static void
handle_synctex_view(zathura_t* zathura, GVariant* parameters,
                    GDBusMethodInvocation* invocation)
{
  gchar* input_file = NULL;
  guint line = 0;
  guint column = 0;
  g_variant_get(parameters, "(suu)", &input_file, &line, &column);
380

381 382
  const bool ret = synctex_view(zathura, input_file, line, column);
  g_free(input_file);
383

384 385 386
  GVariant* result = g_variant_new("(b)", ret);
  g_dbus_method_invocation_return_value(invocation, result);
}
387

388 389 390 391 392 393 394 395 396
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)
{
  ZathuraDbus* dbus = data;
  private_t* priv   = GET_PRIVATE(dbus);
397

398 399
  girara_debug("Handling call '%s.%s' on '%s'.", interface_name, method_name,
               object_path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
400

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
  static const struct {
    const char* method;
    void (*handler)(zathura_t*, GVariant*, GDBusMethodInvocation*);
    bool needs_document;
  } handlers[] = {
    { "OpenDocument", handle_open_document, false },
    { "CloseDocument", handle_close_document, false },
    { "GotoPage", handle_goto_page, true },
    { "HighlightRects", handle_highlight_rects, true },
    { "SynctexView", handle_synctex_view, true }
  };

  for (size_t idx = 0; idx != sizeof(handlers) / sizeof(handlers[0]); ++idx) {
    if (g_strcmp0(method_name, handlers[idx].method) != 0) {
      continue;
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
417

418 419 420 421 422 423 424 425 426
    if (handlers[idx].needs_document == true && priv->zathura->document == NULL) {
      g_dbus_method_invocation_return_dbus_error(
          invocation, "org.pwmt.zathura.NoOpenDocumen",
          "No document has been opened.");
      return;
    }

    (*handlers[idx].handler)(priv->zathura, parameters, invocation);
    return;
427 428 429 430 431
  }
}

static GVariant*
handle_get_property(GDBusConnection* UNUSED(connection),
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
432 433 434 435
                    const gchar* UNUSED(sender),
                    const gchar* UNUSED(object_path),
                    const gchar* UNUSED(interface_name),
                    const gchar* property_name, GError** error, void* data)
436
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
437 438
  ZathuraDbus* dbus = data;
  private_t* priv   = GET_PRIVATE(dbus);
439

440 441 442 443 444
  if (priv->zathura->document == NULL) {
    g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "No document open.");
    return NULL;
  }

445
  if (g_strcmp0(property_name, "filename") == 0) {
446 447 448 449 450
    return g_variant_new_string(zathura_document_get_path(priv->zathura->document));
  } else if (g_strcmp0(property_name, "pagenumber") == 0) {
    return g_variant_new_uint32(zathura_document_get_current_page_number(priv->zathura->document));
  } else if (g_strcmp0(property_name, "numberofpages") == 0) {
    return g_variant_new_uint32(zathura_document_get_number_of_pages(priv->zathura->document));
451 452 453 454 455 456 457 458 459 460 461 462
  }

  return NULL;
}

static const GDBusInterfaceVTable interface_vtable =
{
  .method_call  = handle_method_call,
  .get_property = handle_get_property,
  .set_property = NULL
};

463 464
static const unsigned int TIMEOUT = 3000;

465
static bool
466 467 468
call_synctex_view(GDBusConnection* connection, const char* filename,
                  const char* name, const char* input_file, unsigned int line,
                  unsigned int column)
469 470
{
  GError* error       = NULL;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
471 472 473 474
  GVariant* vfilename = g_dbus_connection_call_sync(
      connection, name, DBUS_OBJPATH, "org.freedesktop.DBus.Properties", "Get",
      g_variant_new("(ss)", DBUS_INTERFACE, "filename"), G_VARIANT_TYPE("(v)"),
      G_DBUS_CALL_FLAGS_NONE, TIMEOUT, NULL, &error);
475 476
  if (vfilename == NULL) {
    girara_error("Failed to query 'filename' property from '%s': %s",
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
477
                  name, error->message);
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
    g_error_free(error);
    return false;
  }

  GVariant* tmp = NULL;
  g_variant_get(vfilename, "(v)", &tmp);
  gchar* remote_filename = g_variant_dup_string(tmp, NULL);
  girara_debug("Filename from '%s': %s", name, remote_filename);
  g_variant_unref(tmp);
  g_variant_unref(vfilename);

  if (g_strcmp0(filename, remote_filename) != 0) {
    g_free(remote_filename);
    return false;
  }

  g_free(remote_filename);

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
496
  GVariant* ret = g_dbus_connection_call_sync(
497 498
      connection, name, DBUS_OBJPATH, DBUS_INTERFACE, "SynctexView",
      g_variant_new("(suu)", input_file, line, column),
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
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 503 504 505 506 507 508 509 510
                 error->message);
    g_error_free(error);
    return false;
  }

  g_variant_unref(ret);
  return true;
}

511
static int
512 513 514
iterate_instances_call_synctex_view(const char* filename,
                                    const char* input_file, unsigned int line,
                                    unsigned int column, pid_t hint)
515
{
516
  if (filename == NULL) {
517
    return -1;
518 519 520
  }

  GError* error = NULL;
521 522
  GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,
                                               &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 530
  if (hint != -1) {
    char* well_known_name = g_strdup_printf(DBUS_NAME_TEMPLATE, hint);
531 532
    const bool ret = call_synctex_view(connection, filename, well_known_name,
                                       input_file, line, column);
533
    g_free(well_known_name);
534
    return ret ? 1 : -1;
535 536
  }

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
537 538 539 540
  GVariant* vnames = g_dbus_connection_call_sync(
      connection, "org.freedesktop.DBus", "/org/freedesktop/DBus",
      "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE("(as)"),
      G_DBUS_CALL_FLAGS_NONE, TIMEOUT, NULL, &error);
541 542 543 544
  if (vnames == NULL) {
    girara_error("Could not list available names: %s", error->message);
    g_error_free(error);
    g_object_unref(connection);
545
    return -1;
546 547 548 549 550 551 552
  }

  GVariantIter* iter = NULL;
  g_variant_get(vnames, "(as)", &iter);

  gchar* name = NULL;
  bool found_one = false;
553
  while (found_one == false && g_variant_iter_loop(iter, "s", &name) == TRUE) {
554 555 556 557 558
    if (g_str_has_prefix(name, "org.pwmt.zathura.PID") == FALSE) {
      continue;
    }
    girara_debug("Found name: %s", name);

559
    found_one = call_synctex_view(connection, filename, name, input_file, line, column);
560 561 562
  }
  g_variant_iter_free(iter);
  g_variant_unref(vnames);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
563
  g_object_unref(connection);
564

565
  return found_one ? 1 : 0;
566 567
}

568
int
569 570
zathura_dbus_synctex_position(const char* filename, const char* input_file,
                              int line, int column, pid_t hint)
571
{
572
  if (filename == NULL || input_file == NULL || line < 0 || column < 0) {
573 574 575
    return false;
  }

576
  return iterate_instances_call_synctex_view(filename, input_file, line, column, hint);
577
}
578