plugin.c 10.2 KB
Newer Older
1
/* SPDX-License-Identifier: Zlib */
2 3 4 5 6 7 8 9 10 11 12 13

#include "plugin.h"

#include <stdlib.h>
#include <glib/gi18n.h>

#include <girara/datastructures.h>
#include <girara/utils.h>
#include <girara/statusbar.h>
#include <girara/session.h>
#include <girara/settings.h>

14 15 16
/**
 * Document plugin structure
 */
Moritz Lipp's avatar
Moritz Lipp committed
17
struct zathura_plugin_s {
18 19 20 21
  girara_list_t* content_types; /**< List of supported content types */
  zathura_plugin_functions_t functions; /**< Document functions */
  GModule* handle; /**< DLL handle */
  char* path; /**< Path to the plugin */
22
  const zathura_plugin_definition_t* definition;
23 24 25 26 27
};

/**
 * Plugin mapping
 */
Moritz Lipp's avatar
Moritz Lipp committed
28
typedef struct zathura_type_plugin_mapping_s {
29 30 31
  const gchar* type; /**< Plugin type */
  zathura_plugin_t* plugin; /**< Mapped plugin */
} zathura_type_plugin_mapping_t;
32

33
/**
Moritz Lipp's avatar
Moritz Lipp committed
34
 * Plugin manager
35
 */
Moritz Lipp's avatar
Moritz Lipp committed
36
struct zathura_plugin_manager_s {
Moritz Lipp's avatar
Moritz Lipp committed
37 38 39 40 41
  girara_list_t* plugins; /**< List of plugins */
  girara_list_t* path; /**< List of plugin paths */
  girara_list_t* type_plugin_mapping; /**< List of type -> plugin mappings */
};

42
static void plugin_add_mimetype(zathura_plugin_t* plugin, const char* mime_type);
Moritz Lipp's avatar
Moritz Lipp committed
43 44 45 46 47 48
static bool register_plugin(zathura_plugin_manager_t* plugin_manager, zathura_plugin_t* plugin);
static bool plugin_mapping_new(zathura_plugin_manager_t* plugin_manager, const gchar* type, zathura_plugin_t* plugin);
static void zathura_plugin_free(zathura_plugin_t* plugin);
static void zathura_type_plugin_mapping_free(zathura_type_plugin_mapping_t* mapping);

zathura_plugin_manager_t*
Sebastian Ramacher's avatar
Sebastian Ramacher committed
49
zathura_plugin_manager_new(void)
Moritz Lipp's avatar
Moritz Lipp committed
50
{
51 52 53 54
  zathura_plugin_manager_t* plugin_manager = g_try_malloc0(sizeof(zathura_plugin_manager_t));
  if (plugin_manager == NULL) {
    return NULL;
  }
Moritz Lipp's avatar
Moritz Lipp committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68

  plugin_manager->plugins = girara_list_new2((girara_free_function_t) zathura_plugin_free);
  plugin_manager->path    = girara_list_new2(g_free);
  plugin_manager->type_plugin_mapping = girara_list_new2((girara_free_function_t)zathura_type_plugin_mapping_free);

  if (plugin_manager->plugins == NULL
      || plugin_manager->path == NULL
      || plugin_manager->type_plugin_mapping == NULL) {
    zathura_plugin_manager_free(plugin_manager);
    return NULL;
  }

  return plugin_manager;
}
69 70

void
Moritz Lipp's avatar
Moritz Lipp committed
71
zathura_plugin_manager_add_dir(zathura_plugin_manager_t* plugin_manager, const char* dir)
72
{
Moritz Lipp's avatar
Moritz Lipp committed
73 74 75 76 77 78 79
  if (plugin_manager == NULL || plugin_manager->path == NULL) {
    return;
  }

  girara_list_append(plugin_manager->path, g_strdup(dir));
}

80 81 82 83 84 85 86
static bool
check_suffix(const char* path)
{
#ifdef __APPLE__
  if (g_str_has_suffix(path, ".dylib") == TRUE) {
    return true;
  }
87 88 89 90
#else
  if (g_str_has_suffix(path, ".so") == TRUE) {
    return true;
  }
91 92 93 94 95
#endif

  return false;
}

96 97 98 99 100
static void
load_plugin(zathura_plugin_manager_t* plugin_manager, const char* plugindir, const char* name)
{
  char* path = g_build_filename(plugindir, name, NULL);
  if (g_file_test(path, G_FILE_TEST_IS_REGULAR) == 0) {
101
    girara_debug("'%s' is not a regular file. Skipping.", path);
102 103 104 105 106
    g_free(path);
    return;
  }

  if (check_suffix(path) == false) {
107
    girara_debug("'%s' is not a plugin file. Skipping.", path);
108 109 110 111 112 113 114
    g_free(path);
    return;
  }

  /* load plugin */
  GModule* handle = g_module_open(path, G_MODULE_BIND_LOCAL);
  if (handle == NULL) {
115
    girara_error("Could not load plugin '%s' (%s).", path, g_module_error());
116 117 118 119 120 121 122 123
    g_free(path);
    return;
  }

  /* resolve symbols and check API and ABI version*/
  const zathura_plugin_definition_t* plugin_definition = NULL;
  if (g_module_symbol(handle, G_STRINGIFY(ZATHURA_PLUGIN_DEFINITION_SYMBOL), (void**) &plugin_definition) == FALSE ||
      plugin_definition == NULL) {
124
    girara_error("Could not find '%s' in plugin %s - is not a plugin or needs to be rebuilt.", G_STRINGIFY(ZATHURA_PLUGIN_DEFINITION_SYMBOL), path);
125 126 127 128 129 130 131
    g_free(path);
    g_module_close(handle);
    return;
  }

  /* check name */
  if (plugin_definition->name == NULL) {
132
    girara_error("Plugin has no name.");
133 134 135 136 137 138 139
    g_free(path);
    g_module_close(handle);
    return;
  }

  /* check mime type */
  if (plugin_definition->mime_types == NULL || plugin_definition->mime_types_size == 0) {
140
    girara_error("Plugin does not handly any mime types.");
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    g_free(path);
    g_module_close(handle);
    return;
  }

  zathura_plugin_t* plugin = g_try_malloc0(sizeof(zathura_plugin_t));
  if (plugin == NULL) {
    girara_error("Failed to allocate memory for plugin.");
    g_free(path);
    g_module_close(handle);
    return;
  }

  plugin->definition = plugin_definition;
  plugin->functions = plugin_definition->functions;
  plugin->content_types = girara_list_new2(g_free);
  plugin->handle = handle;
  plugin->path = path;

  // register mime types
  for (size_t s = 0; s != plugin_definition->mime_types_size; ++s) {
162
    plugin_add_mimetype(plugin, plugin_definition->mime_types[s]);
163 164 165 166
  }

  bool ret = register_plugin(plugin_manager, plugin);
  if (ret == false) {
167
    girara_error("Could not register plugin '%s'.", path);
168 169
    zathura_plugin_free(plugin);
  } else {
170
    girara_debug("Successfully loaded plugin from '%s'.", path);
171 172 173 174 175 176
    girara_debug("plugin %s: version %u.%u.%u", plugin_definition->name,
                 plugin_definition->version.major, plugin_definition->version.minor,
                 plugin_definition->version.rev);
  }
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
static void
load_dir(void* data, void* userdata)
{
  const char* plugindir                    = data;
  zathura_plugin_manager_t* plugin_manager = userdata;

  GDir* dir = g_dir_open(plugindir, 0, NULL);
  if (dir == NULL) {
    girara_error("could not open plugin directory: %s", plugindir);
  } else {
    const char* name = NULL;
    while ((name = g_dir_read_name(dir)) != NULL) {
      load_plugin(plugin_manager, plugindir, name);
    }
    g_dir_close(dir);
  }
}

Moritz Lipp's avatar
Moritz Lipp committed
195 196 197 198 199 200 201
void
zathura_plugin_manager_load(zathura_plugin_manager_t* plugin_manager)
{
  if (plugin_manager == NULL || plugin_manager->path == NULL) {
    return;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
202 203
  /* read all files in the plugin directory */
  girara_list_foreach(plugin_manager->path, load_dir, plugin_manager);
204 205
}

Moritz Lipp's avatar
Moritz Lipp committed
206 207 208 209 210 211 212 213
zathura_plugin_t*
zathura_plugin_manager_get_plugin(zathura_plugin_manager_t* plugin_manager, const char* type)
{
  if (plugin_manager == NULL || plugin_manager->type_plugin_mapping == NULL || type == NULL) {
    return NULL;
  }

  zathura_plugin_t* plugin = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
214 215 216 217 218 219
  GIRARA_LIST_FOREACH_BODY(plugin_manager->type_plugin_mapping, zathura_type_plugin_mapping_t*, mapping,
    if (g_content_type_equals(type, mapping->type)) {
      plugin = mapping->plugin;
      break;
    }
  );
Moritz Lipp's avatar
Moritz Lipp committed
220 221 222 223

  return plugin;
}

224 225 226 227 228 229 230 231 232 233
girara_list_t*
zathura_plugin_manager_get_plugins(zathura_plugin_manager_t* plugin_manager)
{
  if (plugin_manager == NULL || plugin_manager->plugins == NULL) {
    return NULL;
  }

  return plugin_manager->plugins;
}

234
void
Moritz Lipp's avatar
Moritz Lipp committed
235
zathura_plugin_manager_free(zathura_plugin_manager_t* plugin_manager)
236
{
Moritz Lipp's avatar
Moritz Lipp committed
237
  if (plugin_manager == NULL) {
238 239 240
    return;
  }

Moritz Lipp's avatar
Moritz Lipp committed
241 242 243 244 245 246 247 248 249 250 251 252 253
  if (plugin_manager->plugins != NULL) {
    girara_list_free(plugin_manager->plugins);
  }

  if (plugin_manager->path != NULL) {
    girara_list_free(plugin_manager->path);
  }

  if (plugin_manager->type_plugin_mapping != NULL) {
    girara_list_free(plugin_manager->type_plugin_mapping);
  }

  g_free(plugin_manager);
254 255 256
}

static bool
Moritz Lipp's avatar
Moritz Lipp committed
257
register_plugin(zathura_plugin_manager_t* plugin_manager, zathura_plugin_t* plugin)
258
{
Moritz Lipp's avatar
Moritz Lipp committed
259 260 261 262
  if (plugin == NULL
      || plugin->content_types == NULL
      || plugin_manager == NULL
      || plugin_manager->plugins == NULL) {
263
    girara_error("plugin: could not register");
264 265 266
    return false;
  }

Moritz Lipp's avatar
Moritz Lipp committed
267
  bool at_least_one = false;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
268 269
  GIRARA_LIST_FOREACH_BODY(plugin->content_types, gchar*, type,
    if (plugin_mapping_new(plugin_manager, type, plugin) == false) {
270
      girara_error("plugin: filetype already registered: %s", type);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
271
    } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
272
      girara_debug("plugin: filetype mapping added: %s", type);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
273 274 275
      at_least_one = true;
    }
  );
276

Moritz Lipp's avatar
Moritz Lipp committed
277 278
  if (at_least_one == true) {
    girara_list_append(plugin_manager->plugins, plugin);
279
  }
Moritz Lipp's avatar
Moritz Lipp committed
280 281

  return at_least_one;
282 283
}

Moritz Lipp's avatar
Moritz Lipp committed
284 285
static bool
plugin_mapping_new(zathura_plugin_manager_t* plugin_manager, const gchar* type, zathura_plugin_t* plugin)
286
{
Moritz Lipp's avatar
Moritz Lipp committed
287 288 289
  g_return_val_if_fail(plugin_manager != NULL, false);
  g_return_val_if_fail(type           != NULL, false);
  g_return_val_if_fail(plugin         != NULL, false);
290

Sebastian Ramacher's avatar
Sebastian Ramacher committed
291 292
  bool already_registered = false;
  GIRARA_LIST_FOREACH_BODY(plugin_manager->type_plugin_mapping, zathura_type_plugin_mapping_t*, mapping,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
293
    if (g_content_type_equals(type, mapping->type)) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
294 295
      already_registered = true;
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
296 297
    }
  );
298

Sebastian Ramacher's avatar
Sebastian Ramacher committed
299 300 301 302
  if (already_registered == true) {
    return false;
  }

303
  zathura_type_plugin_mapping_t* mapping = g_try_malloc0(sizeof(zathura_type_plugin_mapping_t));
304 305 306 307
  if (mapping == NULL) {
    return false;
  }

Moritz Lipp's avatar
Moritz Lipp committed
308
  mapping->type   = g_strdup(type);
309
  mapping->plugin = plugin;
Moritz Lipp's avatar
Moritz Lipp committed
310 311
  girara_list_append(plugin_manager->type_plugin_mapping, mapping);

312 313 314
  return true;
}

Moritz Lipp's avatar
Moritz Lipp committed
315
static void
316 317 318 319 320 321 322 323 324 325
zathura_type_plugin_mapping_free(zathura_type_plugin_mapping_t* mapping)
{
  if (mapping == NULL) {
    return;
  }

  g_free((void*)mapping->type);
  g_free(mapping);
}

Moritz Lipp's avatar
Moritz Lipp committed
326 327 328 329 330 331
static void
zathura_plugin_free(zathura_plugin_t* plugin)
{
  if (plugin == NULL) {
    return;
  }
332
 
333 334 335 336
  if (plugin->path != NULL) {
    g_free(plugin->path);
  }

337
  g_module_close(plugin->handle);
Moritz Lipp's avatar
Moritz Lipp committed
338
  girara_list_free(plugin->content_types);
339

Moritz Lipp's avatar
Moritz Lipp committed
340 341 342
  g_free(plugin);
}

343
static void
344
plugin_add_mimetype(zathura_plugin_t* plugin, const char* mime_type)
345 346 347 348 349
{
  if (plugin == NULL || mime_type == NULL) {
    return;
  }

350 351 352 353 354 355
  char* content_type = g_content_type_from_mime_type(mime_type);
  if (content_type == NULL) {
    girara_warning("plugin: unable to convert mime type: %s", mime_type);
  } else {
    girara_list_append(plugin->content_types, content_type);
  }
356
}
357

Sebastian Ramacher's avatar
Sebastian Ramacher committed
358
const zathura_plugin_functions_t*
359 360 361
zathura_plugin_get_functions(zathura_plugin_t* plugin)
{
  if (plugin != NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
362
    return &plugin->functions;
363 364 365 366 367
  } else {
    return NULL;
  }
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
368
const char*
369 370
zathura_plugin_get_name(zathura_plugin_t* plugin)
{
371 372
  if (plugin != NULL && plugin->definition != NULL) {
    return plugin->definition->name;
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
  } else {
    return NULL;
  }
}

char*
zathura_plugin_get_path(zathura_plugin_t* plugin)
{
  if (plugin != NULL) {
    return plugin->path;
  } else {
    return NULL;
  }
}

zathura_plugin_version_t
zathura_plugin_get_version(zathura_plugin_t* plugin)
{
391 392
  if (plugin != NULL && plugin->definition != NULL) {
    return plugin->definition->version;
393 394
  }

Moritz Lipp's avatar
Moritz Lipp committed
395
  zathura_plugin_version_t version = { 0, 0, 0 };
396 397
  return version;
}