dbus-interface.c 17.4 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;
}

33 34 35 36 37 38
typedef struct private_s {
  zathura_t* zathura;
  GDBusNodeInfo* introspection_data;
  GDBusConnection* connection;
  guint owner_id;
  guint registration_id;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
39 40 41
} ZathuraDbusPrivate;

G_DEFINE_TYPE_WITH_CODE(ZathuraDbus, zathura_dbus, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraDbus))
42

Sebastian Ramacher's avatar
Sebastian Ramacher committed
43 44 45 46 47 48
/* template for bus name */
static const char DBUS_NAME_TEMPLATE[] = "org.pwmt.zathura.PID-%d";
/* object path */
static const char DBUS_OBJPATH[] = "/org/pwmt/zathura";
/* interface name */
static const char DBUS_INTERFACE[] = "org.pwmt.zathura";
49 50 51 52 53 54

static const GDBusInterfaceVTable interface_vtable;

static void
finalize(GObject* object)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
55 56
  ZathuraDbus* dbus        = ZATHURA_DBUS(object);
  ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
57 58 59 60 61 62 63 64 65 66 67 68 69

  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
70
  G_OBJECT_CLASS(zathura_dbus_parent_class)->finalize(object);
71 72 73
}

static void
Sebastian Ramacher's avatar
Sebastian Ramacher committed
74
zathura_dbus_class_init(ZathuraDbusClass* class)
75 76 77 78 79 80 81
{
  /* overwrite methods */
  GObjectClass* object_class = G_OBJECT_CLASS(class);
  object_class->finalize     = finalize;
}

static void
Sebastian Ramacher's avatar
Sebastian Ramacher committed
82
zathura_dbus_init(ZathuraDbus* dbus)
83
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
84
  ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
85 86 87 88 89 90 91
  priv->zathura            = NULL;
  priv->introspection_data = NULL;
  priv->connection         = NULL;
  priv->owner_id           = 0;
  priv->registration_id    = 0;
}

92 93 94 95 96 97 98 99 100
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);
  }
}

101 102 103 104 105
static void
bus_acquired(GDBusConnection* connection, const gchar* name, void* data)
{
  girara_debug("Bus acquired at '%s'.", name);

106 107 108 109
  /* 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
110 111
  ZathuraDbus* dbus        = data;
  ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
112 113

  GError* error = NULL;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
114 115
  priv->registration_id = g_dbus_connection_register_object(
      connection, DBUS_OBJPATH, priv->introspection_data->interfaces[0],
116 117 118
      &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
119
                   error->message);
120 121 122 123 124 125 126 127 128
    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
129
              void* UNUSED(data))
130 131 132 133 134 135
{
  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
136
          void* UNUSED(data))
137 138
{
  girara_debug("Lost connection or failed to acquire '%s' on session bus.",
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
139
               name);
140 141
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
142 143
ZathuraDbus*
zathura_dbus_new(zathura_t* zathura)
144
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
145
  GObject* obj = g_object_new(ZATHURA_TYPE_DBUS, NULL);
146 147 148 149
  if (obj == NULL) {
    return NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
150
  ZathuraDbus* dbus = ZATHURA_DBUS(obj);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
151
  ZathuraDbusPrivate* priv   = zathura_dbus_get_instance_private(dbus);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
152
  priv->zathura     = zathura;
153

154
  GBytes* xml_data = load_xml_data();
155 156 157 158 159 160 161
  if (xml_data == NULL)
  {
    girara_warning("Failed to load introspection data.");
    g_object_unref(obj);
    return NULL;
  }

162
  GError* error = NULL;
163 164
  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);
165

166 167 168 169 170 171 172 173
  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
174 175 176
  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);
177 178
  g_free(well_known_name);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
179
  return dbus;
180 181
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
182 183
void
zathura_dbus_edit(ZathuraDbus* edit, unsigned int page, unsigned int x, unsigned int y) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
184
  ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(edit);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
185 186 187 188 189 190 191 192 193 194 195 196 197 198

  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,
199
    DBUS_INTERFACE, "Edit", g_variant_new("(suu)", input_file, line, column), &error);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
200 201 202 203 204 205 206 207 208

  g_free(input_file);

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

209 210 211
/* D-Bus handler */

static void
212 213
handle_open_document(zathura_t* zathura, GVariant* parameters,
                     GDBusMethodInvocation* invocation)
214
{
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
  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);
}
231

232 233 234 235 236
static void
handle_close_document(zathura_t* zathura, GVariant* UNUSED(parameters),
                      GDBusMethodInvocation* invocation)
{
  const bool ret = document_close(zathura, false);
237

238 239 240
  GVariant* result = g_variant_new("(b)", ret);
  g_dbus_method_invocation_return_value(invocation, result);
}
241

242 243 244 245 246
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);
247

248 249 250 251 252 253 254 255
  guint page = 0;
  g_variant_get(parameters, "(u)", &page);

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

258 259 260
  GVariant* result = g_variant_new("(b)", ret);
  g_dbus_method_invocation_return_value(invocation, result);
}
261

262 263 264 265 266
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);
267

268 269 270 271 272
  guint page = 0;
  GVariantIter* iter = NULL;
  GVariantIter* secondary_iter = NULL;
  g_variant_get(parameters, "(ua(dddd)a(udddd))", &page, &iter,
                &secondary_iter);
273

274 275 276 277 278
  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);
279
    g_dbus_method_invocation_return_value(invocation, result);
280 281 282 283 284 285 286 287 288 289 290 291 292
    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;
  }
293

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
  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) {
310 311
      g_variant_iter_free(iter);
      g_variant_iter_free(secondary_iter);
312 313
      girara_list_free(rectangles[page]);
      g_free(rectangles);
314 315 316
      g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                            G_DBUS_ERROR_NO_MEMORY,
                                            "Failed to allocate memory.");
317 318 319
      return;
    }

320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
    *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) {
342
      g_variant_iter_free(secondary_iter);
343 344 345 346 347
      for (unsigned int p = 0; p != number_of_pages; ++p) {
        girara_list_free(rectangles[p]);
      }
      g_free(rectangles);
      g_free(rect);
348 349 350 351 352
      g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                            G_DBUS_ERROR_NO_MEMORY,
                                            "Failed to allocate memory.");
      return;
    }
353

354 355 356 357
    *rect = temp_rect;
    girara_list_append(rectangles[temp_page], rect);
  }
  g_variant_iter_free(secondary_iter);
358

359 360
  synctex_highlight_rects(zathura, page, rectangles);
  g_free(rectangles);
361

362 363 364
  GVariant* result = g_variant_new("(b)", true);
  g_dbus_method_invocation_return_value(invocation, result);
}
365

366 367 368 369 370 371 372 373
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);
374

375 376
  const bool ret = synctex_view(zathura, input_file, line, column);
  g_free(input_file);
377

378 379 380
  GVariant* result = g_variant_new("(b)", ret);
  g_dbus_method_invocation_return_value(invocation, result);
}
381

382 383 384 385 386 387 388 389
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;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
390
  ZathuraDbusPrivate* priv   = zathura_dbus_get_instance_private(dbus);
391

392 393
  girara_debug("Handling call '%s.%s' on '%s'.", interface_name, method_name,
               object_path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
394

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
  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
411

412 413
    if (handlers[idx].needs_document == true && priv->zathura->document == NULL) {
      g_dbus_method_invocation_return_dbus_error(
Sebastian Ramacher's avatar
Sebastian Ramacher committed
414
          invocation, "org.pwmt.zathura.NoOpenDocument",
415 416 417 418 419 420
          "No document has been opened.");
      return;
    }

    (*handlers[idx].handler)(priv->zathura, parameters, invocation);
    return;
421 422 423 424 425
  }
}

static GVariant*
handle_get_property(GDBusConnection* UNUSED(connection),
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
426 427 428 429
                    const gchar* UNUSED(sender),
                    const gchar* UNUSED(object_path),
                    const gchar* UNUSED(interface_name),
                    const gchar* property_name, GError** error, void* data)
430
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
431 432
  ZathuraDbus* dbus        = data;
  ZathuraDbusPrivate* priv = zathura_dbus_get_instance_private(dbus);
433

434 435 436 437 438
  if (priv->zathura->document == NULL) {
    g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "No document open.");
    return NULL;
  }

439
  if (g_strcmp0(property_name, "filename") == 0) {
440 441 442 443 444
    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));
445 446 447 448 449 450 451 452 453 454 455 456
  }

  return NULL;
}

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

457 458
static const unsigned int TIMEOUT = 3000;

459
static bool
460 461 462
call_synctex_view(GDBusConnection* connection, const char* filename,
                  const char* name, const char* input_file, unsigned int line,
                  unsigned int column)
463 464
{
  GError* error       = NULL;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
465 466 467 468
  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);
469 470
  if (vfilename == NULL) {
    girara_error("Failed to query 'filename' property from '%s': %s",
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
471
                  name, error->message);
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
    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
490
  GVariant* ret = g_dbus_connection_call_sync(
491 492
      connection, name, DBUS_OBJPATH, DBUS_INTERFACE, "SynctexView",
      g_variant_new("(suu)", input_file, line, column),
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
493
      G_VARIANT_TYPE("(b)"), G_DBUS_CALL_FLAGS_NONE, TIMEOUT, NULL, &error);
494
  if (ret == NULL) {
495
    girara_error("Failed to run SynctexView on '%s': %s", name,
496 497 498 499 500 501 502 503 504
                 error->message);
    g_error_free(error);
    return false;
  }

  g_variant_unref(ret);
  return true;
}

505
static int
506 507 508
iterate_instances_call_synctex_view(const char* filename,
                                    const char* input_file, unsigned int line,
                                    unsigned int column, pid_t hint)
509
{
510
  if (filename == NULL) {
511
    return -1;
512 513 514
  }

  GError* error = NULL;
515 516
  GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,
                                               &error);
517
  if (connection == NULL) {
518
    girara_error("Could not connect to session bus: %s", error->message);
519
    g_error_free(error);
520
    return -1;
521 522
  }

523 524
  if (hint != -1) {
    char* well_known_name = g_strdup_printf(DBUS_NAME_TEMPLATE, hint);
525 526
    const bool ret = call_synctex_view(connection, filename, well_known_name,
                                       input_file, line, column);
527
    g_free(well_known_name);
528
    return ret ? 1 : -1;
529 530
  }

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
531 532 533 534
  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);
535 536 537 538
  if (vnames == NULL) {
    girara_error("Could not list available names: %s", error->message);
    g_error_free(error);
    g_object_unref(connection);
539
    return -1;
540 541 542 543 544 545 546
  }

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

  gchar* name = NULL;
  bool found_one = false;
547
  while (found_one == false && g_variant_iter_loop(iter, "s", &name) == TRUE) {
548 549 550 551 552
    if (g_str_has_prefix(name, "org.pwmt.zathura.PID") == FALSE) {
      continue;
    }
    girara_debug("Found name: %s", name);

553
    found_one = call_synctex_view(connection, filename, name, input_file, line, column);
554 555 556
  }
  g_variant_iter_free(iter);
  g_variant_unref(vnames);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
557
  g_object_unref(connection);
558

559
  return found_one ? 1 : 0;
560 561
}

562
int
563 564
zathura_dbus_synctex_position(const char* filename, const char* input_file,
                              int line, int column, pid_t hint)
565
{
566
  if (filename == NULL || input_file == NULL || line < 0 || column < 0) {
567 568 569
    return false;
  }

570
  return iterate_instances_call_synctex_view(filename, input_file, line, column, hint);
571
}