dbus-interface.c 16.1 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
#include "dbus-interface-definitions.h"
5 6 7 8
#include "synctex.h"
#include "macros.h"
#include "zathura.h"
#include "document.h"
9
#include "utils.h"
10
#include "adjustment.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>

Sebastian Ramacher's avatar
Sebastian Ramacher committed
20
G_DEFINE_TYPE(ZathuraDbus, zathura_dbus, G_TYPE_OBJECT)
21 22 23

/* template for bus name */
static const char DBUS_NAME_TEMPLATE[] = "org.pwmt.zathura.PID-%d";
24
/* object path */
25
static const char DBUS_OBJPATH[] = "/org/pwmt/zathura";
26
/* interface name */
27
static const char DBUS_INTERFACE[] = "org.pwmt.zathura";
28 29 30 31 32 33 34 35 36 37

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
38
  (G_TYPE_INSTANCE_GET_PRIVATE((obj), ZATHURA_TYPE_DBUS, private_t))
39 40 41 42 43 44

static const GDBusInterfaceVTable interface_vtable;

static void
finalize(GObject* object)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
45 46
  ZathuraDbus* dbus = ZATHURA_DBUS(object);
  private_t* priv   = GET_PRIVATE(dbus);
47 48 49 50 51 52 53 54 55 56 57 58 59

  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
60
  G_OBJECT_CLASS(zathura_dbus_parent_class)->finalize(object);
61 62 63
}

static void
Sebastian Ramacher's avatar
Sebastian Ramacher committed
64
zathura_dbus_class_init(ZathuraDbusClass* class)
65 66 67 68 69 70 71 72 73 74
{
  /* 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
75
zathura_dbus_init(ZathuraDbus* dbus)
76
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
77
  private_t* priv          = GET_PRIVATE(dbus);
78 79 80 81 82 83 84
  priv->zathura            = NULL;
  priv->introspection_data = NULL;
  priv->connection         = NULL;
  priv->owner_id           = 0;
  priv->registration_id    = 0;
}

85 86 87 88 89 90 91 92 93
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);
  }
}

94 95 96 97 98
static void
bus_acquired(GDBusConnection* connection, const gchar* name, void* data)
{
  girara_debug("Bus acquired at '%s'.", name);

99 100 101 102
  /* 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
103 104
  ZathuraDbus* dbus = data;
  private_t* priv   = GET_PRIVATE(dbus);
105 106

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
135 136
ZathuraDbus*
zathura_dbus_new(zathura_t* zathura)
137
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
138
  GObject* obj = g_object_new(ZATHURA_TYPE_DBUS, NULL);
139 140 141 142
  if (obj == NULL) {
    return NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
143 144 145
  ZathuraDbus* dbus = ZATHURA_DBUS(obj);
  private_t* priv   = GET_PRIVATE(dbus);
  priv->zathura     = zathura;
146 147

  GError* error = NULL;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
148 149
  priv->introspection_data = g_dbus_node_info_new_for_xml(DBUS_INTERFACE_XML,
                                                          &error);
150 151 152 153 154 155 156 157
  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
158 159 160
  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);
161 162
  g_free(well_known_name);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
163
  return dbus;
164 165
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
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);
  }
}

193 194 195 196
/* D-Bus handler */

static void
handle_method_call(GDBusConnection* UNUSED(connection),
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
197 198 199 200
                   const gchar* UNUSED(sender), const gchar* object_path,
                   const gchar* interface_name, const gchar* method_name,
                   GVariant* parameters, GDBusMethodInvocation* invocation,
                   void* data)
201
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
202 203
  ZathuraDbus* dbus = data;
  private_t* priv   = GET_PRIVATE(dbus);
204

205 206 207 208 209 210 211 212 213 214 215
  girara_debug("Handling call '%s.%s' on '%s'.", interface_name, method_name,
               object_path);

  /* methods that work without open document */
  if (g_strcmp0(method_name, "OpenDocument") == 0) {
    gchar* filename = NULL;
    gchar* password = NULL;
    gint page = ZATHURA_PAGE_NUMBER_UNSPECIFIED;
    g_variant_get(parameters, "(ssi)", &filename, &password, &page);

    document_close(priv->zathura, false);
Lukas K.'s avatar
Lukas K. committed
216 217 218 219
    document_open_idle(priv->zathura, filename,
                       strlen(password) > 0 ? password : NULL,
                       page,
                       NULL, NULL);
220 221 222
    g_free(filename);
    g_free(password);

Lukas K.'s avatar
Lukas K. committed
223
    GVariant* result = g_variant_new("(b)", true);
224 225 226 227
    g_dbus_method_invocation_return_value(invocation, result);
    return;
  } else if (g_strcmp0(method_name, "CloseDocument") == 0) {
    const bool ret = document_close(priv->zathura, false);
228

229 230 231 232
    GVariant* result = g_variant_new("(b)", ret);
    g_dbus_method_invocation_return_value(invocation, result);
    return;
  }
233

234 235
  if (priv->zathura->document == NULL) {
    g_dbus_method_invocation_return_dbus_error(invocation,
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
236 237
                                               "org.pwmt.zathura.NoOpenDocumen",
                                               "No document has been opened.");
238 239 240 241 242 243 244
    return;
  }

  const unsigned int number_of_pages = zathura_document_get_number_of_pages(priv->zathura->document);

  /* methods that require an open document */
  if (g_strcmp0(method_name, "GotoPage") == 0) {
245 246
    guint page = 0;
    g_variant_get(parameters, "(u)", &page);
247 248

    bool ret = true;
249
    if (page >= number_of_pages) {
250 251
      ret = false;
    } else {
252
      page_set(priv->zathura, page);
253 254 255 256 257
    }

    GVariant* result = g_variant_new("(b)", ret);
    g_dbus_method_invocation_return_value(invocation, result);
  } else if (g_strcmp0(method_name, "HighlightRects") == 0) {
258
    guint page = 0;
259
    GVariantIter* iter = NULL;
260
    GVariantIter* secondary_iter = NULL;
261
    g_variant_get(parameters, "(ua(dddd)a(udddd))", &page, &iter,
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
262
                  &secondary_iter);
263

264
    if (page >= number_of_pages) {
265
      girara_debug("Got invalid page number.");
266
      GVariant* result = g_variant_new("(b)", false);
267 268
      g_variant_iter_free(iter);
      g_variant_iter_free(secondary_iter);
269
      g_dbus_method_invocation_return_value(invocation, result);
270
      return;
271 272 273
    }

    /* get rectangles */
274 275
    girara_list_t** rectangles = g_try_malloc0(number_of_pages * sizeof(girara_list_t*));
    if (rectangles == NULL) {
276 277 278 279 280
      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.");
281 282 283
      return;
    }

284
    rectangles[page] = girara_list_new2(g_free);
285 286 287 288 289 290 291 292 293
    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;
    }
294

Sebastian Ramacher's avatar
Sebastian Ramacher committed
295
    zathura_rectangle_t temp_rect = { 0, 0, 0, 0 };
296
    while (g_variant_iter_loop(iter, "(dddd)", &temp_rect.x1, &temp_rect.x2,
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
297
                               &temp_rect.y1, &temp_rect.y2)) {
298 299
      zathura_rectangle_t* rect = g_try_malloc0(sizeof(zathura_rectangle_t));
      if (rect == NULL) {
300 301 302 303 304 305 306 307
        g_variant_iter_free(iter);
        g_variant_iter_free(secondary_iter);
        girara_list_free(rectangles[page]);
        g_free(rectangles);
        g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                              G_DBUS_ERROR_NO_MEMORY,
                                              "Failed to allocate memory.");
        return;
308
      }
309

310
      *rect = temp_rect;
311
      girara_list_append(rectangles[page], rect);
312 313 314
    }
    g_variant_iter_free(iter);

315
    /* get secondary rectangles */
316 317
    guint temp_page = 0;
    while (g_variant_iter_loop(secondary_iter, "(udddd)", &temp_page,
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
318 319
                               &temp_rect.x1, &temp_rect.x2, &temp_rect.y1,
                               &temp_rect.y2)) {
320
      if (temp_page >= number_of_pages) {
321
        /* error out here? */
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
322
        girara_debug("Got invalid page number.");
323 324 325 326 327 328
        continue;
      }

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

330
      zathura_rectangle_t* rect = g_try_malloc0(sizeof(zathura_rectangle_t));
331 332 333 334 335 336 337 338 339 340 341
      if (rect == NULL || rectangles[temp_page] == NULL) {
        g_variant_iter_free(secondary_iter);
        for (unsigned int p = 0; p != number_of_pages; ++p) {
          girara_list_free(rectangles[p]);
        }
        g_free(rectangles);
        g_free(rect);
        g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                              G_DBUS_ERROR_NO_MEMORY,
                                              "Failed to allocate memory.");
        return;
342
      }
343

344
      *rect = temp_rect;
345 346 347 348
      girara_list_append(rectangles[temp_page], rect);
    }
    g_variant_iter_free(secondary_iter);

349
    synctex_highlight_rects(priv->zathura, page, rectangles);
350
    g_free(rectangles);
351 352 353

    GVariant* result = g_variant_new("(b)", true);
    g_dbus_method_invocation_return_value(invocation, result);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
354 355 356 357 358 359
  } else if (g_strcmp0(method_name, "SynctexView") == 0) {
    gchar* input_file = NULL;
    guint line = 0;
    guint column = 0;
    g_variant_get(parameters, "(suu)", &input_file, &line, &column);

360
    const bool ret = synctex_view(priv->zathura, input_file, line, column);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
361 362
    g_free(input_file);

363
    GVariant* result = g_variant_new("(b)", ret);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
364
    g_dbus_method_invocation_return_value(invocation, result);
365 366 367 368 369
  }
}

static GVariant*
handle_get_property(GDBusConnection* UNUSED(connection),
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
370 371 372 373
                    const gchar* UNUSED(sender),
                    const gchar* UNUSED(object_path),
                    const gchar* UNUSED(interface_name),
                    const gchar* property_name, GError** error, void* data)
374
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
375 376
  ZathuraDbus* dbus = data;
  private_t* priv   = GET_PRIVATE(dbus);
377

378 379 380 381 382
  if (priv->zathura->document == NULL) {
    g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "No document open.");
    return NULL;
  }

383
  if (g_strcmp0(property_name, "filename") == 0) {
384 385 386 387 388
    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));
389 390 391 392 393 394 395 396 397 398 399 400
  }

  return NULL;
}

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

401 402
static const unsigned int TIMEOUT = 3000;

403
static bool
404 405 406
call_synctex_view(GDBusConnection* connection, const char* filename,
                  const char* name, const char* input_file, unsigned int line,
                  unsigned int column)
407 408
{
  GError* error       = NULL;
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
409 410 411 412
  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);
413 414
  if (vfilename == NULL) {
    girara_error("Failed to query 'filename' property from '%s': %s",
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
415
                  name, error->message);
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
    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
434
  GVariant* ret = g_dbus_connection_call_sync(
435 436
      connection, name, DBUS_OBJPATH, DBUS_INTERFACE, "SynctexView",
      g_variant_new("(suu)", input_file, line, column),
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
437
      G_VARIANT_TYPE("(b)"), G_DBUS_CALL_FLAGS_NONE, TIMEOUT, NULL, &error);
438
  if (ret == NULL) {
439
    girara_error("Failed to run SynctexView on '%s': %s", name,
440 441 442 443 444 445 446 447 448
                 error->message);
    g_error_free(error);
    return false;
  }

  g_variant_unref(ret);
  return true;
}

449
static int
450 451 452
iterate_instances_call_synctex_view(const char* filename,
                                    const char* input_file, unsigned int line,
                                    unsigned int column, pid_t hint)
453
{
454
  if (filename == NULL) {
455
    return -1;
456 457 458
  }

  GError* error = NULL;
459 460
  GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL,
                                               &error);
461
  if (connection == NULL) {
462
    girara_error("Could not connect to session bus: %s", error->message);
463
    g_error_free(error);
464
    return -1;
465 466
  }

467 468
  if (hint != -1) {
    char* well_known_name = g_strdup_printf(DBUS_NAME_TEMPLATE, hint);
469 470
    const bool ret = call_synctex_view(connection, filename, well_known_name,
                                       input_file, line, column);
471
    g_free(well_known_name);
472
    return ret ? 1 : -1;
473 474
  }

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
475 476 477 478
  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);
479 480 481 482
  if (vnames == NULL) {
    girara_error("Could not list available names: %s", error->message);
    g_error_free(error);
    g_object_unref(connection);
483
    return -1;
484 485 486 487 488 489 490
  }

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

  gchar* name = NULL;
  bool found_one = false;
491
  while (found_one == false && g_variant_iter_loop(iter, "s", &name) == TRUE) {
492 493 494 495 496
    if (g_str_has_prefix(name, "org.pwmt.zathura.PID") == FALSE) {
      continue;
    }
    girara_debug("Found name: %s", name);

497
    found_one = call_synctex_view(connection, filename, name, input_file, line, column);
498 499 500
  }
  g_variant_iter_free(iter);
  g_variant_unref(vnames);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
501
  g_object_unref(connection);
502

503
  return found_one ? 1 : 0;
504 505
}

506
int
507 508
zathura_dbus_synctex_position(const char* filename, const char* input_file,
                              int line, int column, pid_t hint)
509
{
510
  if (filename == NULL || input_file == NULL || line < 0 || column < 0) {
511 512 513
    return false;
  }

514
  return iterate_instances_call_synctex_view(filename, input_file, line, column, hint);
515
}
516