aboutsummaryrefslogtreecommitdiff
path: root/src/plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugin.c')
-rw-r--r--src/plugin.c613
1 files changed, 613 insertions, 0 deletions
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 0000000..ac849d1
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,613 @@
+#include "plugin.h"
+
+#include "common.h"
+#include "ecex.h"
+#include "util.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct ecex_plugin_slot {
+ char *name;
+ void *data;
+ size_t elem_size;
+ size_t count;
+ int type;
+ int exported;
+ int export_flags;
+} ecex_plugin_slot_t;
+
+typedef struct ecex_plugin_object {
+ char *name;
+ void *ptr;
+ size_t size;
+} ecex_plugin_object_t;
+
+typedef struct ecex_plugin_text {
+ int id;
+ char *text;
+ size_t len;
+} ecex_plugin_text_t;
+
+typedef struct ecex_plugin_file_handler {
+ char *extension;
+ ecex_file_handler_fn fn;
+} ecex_plugin_file_handler_t;
+
+struct ecex_plugin {
+ ecex_t *ed;
+ char *id;
+ int api_version;
+
+ ecex_plugin_slot_t *slots;
+ size_t slot_count;
+ size_t slot_cap;
+
+ ecex_plugin_object_t *objects;
+ size_t object_count;
+ size_t object_cap;
+
+ ecex_plugin_text_t *texts;
+ size_t text_count;
+ size_t text_cap;
+
+ ecex_plugin_file_handler_t *file_handlers;
+ size_t file_handler_count;
+ size_t file_handler_cap;
+};
+
+struct ecex_plugin_runtime {
+ ecex_plugin_t **plugins;
+ size_t plugin_count;
+ size_t plugin_cap;
+};
+
+static int plugin_id_valid(const char *id) {
+ if (!id || !id[0]) return 0;
+ for (const unsigned char *p = (const unsigned char *)id; *p; ++p) {
+ if ((*p >= 'a' && *p <= 'z') ||
+ (*p >= '0' && *p <= '9') ||
+ *p == '-' || *p == '_' || *p == '.') {
+ continue;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+static int str_eq(const char *a, const char *b) {
+ if (!a) a = "";
+ if (!b) b = "";
+ return strcmp(a, b) == 0;
+}
+
+static int ascii_equal_ci(const char *a, const char *b) {
+ unsigned char ca;
+ unsigned char cb;
+ if (!a || !b) return 0;
+ while (*a && *b) {
+ ca = (unsigned char)*a;
+ cb = (unsigned char)*b;
+ if (ca >= 'A' && ca <= 'Z') ca = (unsigned char)(ca - 'A' + 'a');
+ if (cb >= 'A' && cb <= 'Z') cb = (unsigned char)(cb - 'A' + 'a');
+ if (ca != cb) return 0;
+ ++a;
+ ++b;
+ }
+ return *a == '\0' && *b == '\0';
+}
+
+static const char *path_extension(const char *path) {
+ const char *dot;
+ if (!path) return NULL;
+ dot = strrchr(path, '.');
+ return dot && dot[0] ? dot : NULL;
+}
+
+static void slot_clear(ecex_plugin_slot_t *slot) {
+ if (!slot) return;
+ free(slot->name);
+ free(slot->data);
+ memset(slot, 0, sizeof(*slot));
+}
+
+static void object_clear(ecex_plugin_object_t *object) {
+ if (!object) return;
+ free(object->name);
+ free(object->ptr);
+ memset(object, 0, sizeof(*object));
+}
+
+static void text_clear(ecex_plugin_text_t *text) {
+ if (!text) return;
+ free(text->text);
+ memset(text, 0, sizeof(*text));
+}
+
+static void file_handler_clear(ecex_plugin_file_handler_t *handler) {
+ if (!handler) return;
+ free(handler->extension);
+ memset(handler, 0, sizeof(*handler));
+}
+
+static void plugin_free(ecex_plugin_t *plugin) {
+ if (!plugin) return;
+ for (size_t i = 0; i < plugin->slot_count; ++i) slot_clear(&plugin->slots[i]);
+ for (size_t i = 0; i < plugin->object_count; ++i) object_clear(&plugin->objects[i]);
+ for (size_t i = 0; i < plugin->text_count; ++i) text_clear(&plugin->texts[i]);
+ for (size_t i = 0; i < plugin->file_handler_count; ++i) file_handler_clear(&plugin->file_handlers[i]);
+ free(plugin->slots);
+ free(plugin->objects);
+ free(plugin->texts);
+ free(plugin->file_handlers);
+ free(plugin->id);
+ free(plugin);
+}
+
+ecex_plugin_runtime_t *ecex_plugin_runtime_new(void) {
+ return calloc(1, sizeof(ecex_plugin_runtime_t));
+}
+
+void ecex_plugin_runtime_free(ecex_plugin_runtime_t *runtime) {
+ if (!runtime) return;
+ for (size_t i = 0; i < runtime->plugin_count; ++i) {
+ plugin_free(runtime->plugins[i]);
+ }
+ free(runtime->plugins);
+ free(runtime);
+}
+
+static ecex_plugin_slot_t *slot_find(ecex_plugin_t *plugin, const char *name) {
+ if (!plugin || !name) return NULL;
+ for (size_t i = 0; i < plugin->slot_count; ++i) {
+ if (str_eq(plugin->slots[i].name, name)) return &plugin->slots[i];
+ }
+ return NULL;
+}
+
+static ecex_plugin_object_t *object_find(ecex_plugin_t *plugin, void *object) {
+ if (!plugin || !object) return NULL;
+ for (size_t i = 0; i < plugin->object_count; ++i) {
+ if (plugin->objects[i].ptr == object) return &plugin->objects[i];
+ }
+ return NULL;
+}
+
+static ecex_plugin_text_t *text_find(ecex_plugin_t *plugin, int id) {
+ if (!plugin) return NULL;
+ for (size_t i = 0; i < plugin->text_count; ++i) {
+ if (plugin->texts[i].id == id) return &plugin->texts[i];
+ }
+ return NULL;
+}
+
+ecex_plugin_t *ecex_plugin_find(ecex_t *ed, const char *id) {
+ if (!ed || !ed->plugins || !id) return NULL;
+ for (size_t i = 0; i < ed->plugins->plugin_count; ++i) {
+ ecex_plugin_t *plugin = ed->plugins->plugins[i];
+ if (plugin && str_eq(plugin->id, id)) return plugin;
+ }
+ return NULL;
+}
+
+ecex_plugin_t *ecex_plugin_register(ecex_t *ed, const char *id, int api_version) {
+ ecex_plugin_t *plugin;
+ if (!ed || !ed->plugins || !plugin_id_valid(id)) return NULL;
+ if (api_version != ECEX_PLUGIN_API_VERSION) return NULL;
+ if (ecex_plugin_find(ed, id)) return NULL;
+ if (ECEX_GROW_ARRAY(ed->plugins->plugins,
+ ed->plugins->plugin_count,
+ ed->plugins->plugin_cap,
+ 8) != ECEX_OK) {
+ return NULL;
+ }
+ plugin = calloc(1, sizeof(*plugin));
+ if (!plugin) return NULL;
+ plugin->id = ecex_strdup(id);
+ if (!plugin->id) {
+ free(plugin);
+ return NULL;
+ }
+ plugin->ed = ed;
+ plugin->api_version = api_version;
+ ed->plugins->plugins[ed->plugins->plugin_count++] = plugin;
+ return plugin;
+}
+
+ecex_plugin_t *ecex_plugin_require(ecex_t *ed, const char *id, int api_version) {
+ ecex_plugin_t *plugin;
+ if (api_version != ECEX_PLUGIN_API_VERSION) return NULL;
+ plugin = ecex_plugin_find(ed, id);
+ return plugin ? plugin : ecex_plugin_register(ed, id, api_version);
+}
+
+const char *ecex_plugin_id(ecex_plugin_t *plugin) {
+ return plugin ? plugin->id : NULL;
+}
+
+void *ecex_plugin_slot_alloc(ecex_plugin_t *plugin,
+ const char *name,
+ size_t count,
+ size_t elem_size) {
+ ecex_plugin_slot_t *slot;
+ void *data;
+ if (!plugin || !name || !name[0]) return NULL;
+ if (count == 0) count = 1;
+ if (elem_size == 0) elem_size = 1;
+ if (count > ((size_t)-1) / elem_size) return NULL;
+
+ slot = slot_find(plugin, name);
+ if (slot) {
+ if (slot->elem_size != elem_size) return NULL;
+ if (slot->count < count) {
+ data = realloc(slot->data, count * elem_size);
+ if (!data) return NULL;
+ memset((char *)data + slot->count * elem_size, 0, (count - slot->count) * elem_size);
+ slot->data = data;
+ slot->count = count;
+ }
+ return slot->data;
+ }
+
+ if (ECEX_GROW_ARRAY(plugin->slots,
+ plugin->slot_count,
+ plugin->slot_cap,
+ 16) != ECEX_OK) {
+ return NULL;
+ }
+ data = calloc(count, elem_size);
+ if (!data) return NULL;
+
+ slot = &plugin->slots[plugin->slot_count++];
+ memset(slot, 0, sizeof(*slot));
+ slot->name = ecex_strdup(name);
+ if (!slot->name) {
+ free(data);
+ --plugin->slot_count;
+ return NULL;
+ }
+ slot->data = data;
+ slot->count = count;
+ slot->elem_size = elem_size;
+ slot->type = elem_size == sizeof(int) ? ECEX_PLUGIN_I32 : ECEX_PLUGIN_BYTES;
+ return slot->data;
+}
+
+void *ecex_plugin_slot_get(ecex_plugin_t *plugin, const char *name) {
+ ecex_plugin_slot_t *slot = slot_find(plugin, name);
+ return slot ? slot->data : NULL;
+}
+
+int ecex_plugin_slot_free(ecex_plugin_t *plugin, const char *name) {
+ if (!plugin || !name) return ECEX_ERR;
+ for (size_t i = 0; i < plugin->slot_count; ++i) {
+ if (!str_eq(plugin->slots[i].name, name)) continue;
+ slot_clear(&plugin->slots[i]);
+ if (i + 1 < plugin->slot_count) {
+ memmove(&plugin->slots[i], &plugin->slots[i + 1],
+ (plugin->slot_count - i - 1) * sizeof(plugin->slots[i]));
+ }
+ --plugin->slot_count;
+ return ECEX_OK;
+ }
+ return ECEX_ERR;
+}
+
+int ecex_plugin_slot_set_export_flags(ecex_plugin_t *plugin,
+ const char *name,
+ int type,
+ int flags) {
+ ecex_plugin_slot_t *slot = slot_find(plugin, name);
+ if (!slot) return ECEX_ERR;
+ slot->type = type;
+ slot->exported = 1;
+ slot->export_flags = flags;
+ return ECEX_OK;
+}
+
+int ecex_plugin_slot_read_exported(ecex_t *ed,
+ const char *plugin_id,
+ const char *name,
+ void *out,
+ size_t out_cap,
+ size_t *out_len) {
+ ecex_plugin_t *plugin = ecex_plugin_find(ed, plugin_id);
+ ecex_plugin_slot_t *slot = slot_find(plugin, name);
+ size_t len;
+ if (out_len) *out_len = 0;
+ if (!slot || !slot->exported || !(slot->export_flags & ECEX_PLUGIN_EXPORT_READ)) return ECEX_ERR;
+ len = slot->count * slot->elem_size;
+ if (out_len) *out_len = len;
+ if (!out || out_cap < len) return ECEX_ERR;
+ if (len) memcpy(out, slot->data, len);
+ return ECEX_OK;
+}
+
+int ecex_plugin_slot_i32_get(ecex_plugin_t *plugin,
+ const char *name,
+ size_t index,
+ int fallback) {
+ ecex_plugin_slot_t *slot = slot_find(plugin, name);
+ if (!slot || !slot->data || slot->elem_size != sizeof(int) || index >= slot->count) return fallback;
+ return ((int *)slot->data)[index];
+}
+
+int ecex_plugin_slot_i32_get_scalar(ecex_plugin_t *plugin, const char *name, int fallback) {
+ return ecex_plugin_slot_i32_get(plugin, name, 0, fallback);
+}
+
+int ecex_plugin_slot_i32_set(ecex_plugin_t *plugin,
+ const char *name,
+ size_t index,
+ int value) {
+ int *data;
+ if (!plugin || !name) return ECEX_ERR;
+ data = (int *)ecex_plugin_slot_alloc(plugin, name, index + 1, sizeof(int));
+ if (!data) return ECEX_ERR;
+ data[index] = value;
+ return ECEX_OK;
+}
+
+static int slot_index_2d(size_t width, size_t x, size_t y, size_t *out) {
+ if (!out || width == 0 || x >= width) return ECEX_ERR;
+ if (y > (((size_t)-1) - x) / width) return ECEX_ERR;
+ *out = y * width + x;
+ return ECEX_OK;
+}
+
+int ecex_plugin_slot_i32_get_2d(ecex_plugin_t *plugin,
+ const char *name,
+ size_t width,
+ size_t x,
+ size_t y,
+ int fallback) {
+ size_t index;
+ if (slot_index_2d(width, x, y, &index) != ECEX_OK) return fallback;
+ return ecex_plugin_slot_i32_get(plugin, name, index, fallback);
+}
+
+int ecex_plugin_slot_i32_set_2d(ecex_plugin_t *plugin,
+ const char *name,
+ size_t width,
+ size_t x,
+ size_t y,
+ int value) {
+ size_t index;
+ if (slot_index_2d(width, x, y, &index) != ECEX_OK) return ECEX_ERR;
+ return ecex_plugin_slot_i32_set(plugin, name, index, value);
+}
+
+int ecex_plugin_slot_i32_set_scalar(ecex_plugin_t *plugin,
+ const char *name,
+ int value) {
+ return ecex_plugin_slot_i32_set(plugin, name, 0, value);
+}
+
+void *ecex_plugin_object_alloc(ecex_plugin_t *plugin, const char *name, size_t size) {
+ ecex_plugin_object_t *object;
+ void *ptr;
+ if (!plugin) return NULL;
+ if (size == 0) size = 1;
+ if (ECEX_GROW_ARRAY(plugin->objects,
+ plugin->object_count,
+ plugin->object_cap,
+ 16) != ECEX_OK) {
+ return NULL;
+ }
+ ptr = malloc(size);
+ if (!ptr) return NULL;
+ object = &plugin->objects[plugin->object_count++];
+ memset(object, 0, sizeof(*object));
+ object->name = ecex_strdup(name ? name : "");
+ if (!object->name) {
+ free(ptr);
+ --plugin->object_count;
+ return NULL;
+ }
+ object->ptr = ptr;
+ object->size = size;
+ return ptr;
+}
+
+void *ecex_plugin_object_calloc(ecex_plugin_t *plugin,
+ const char *name,
+ size_t count,
+ size_t size) {
+ void *ptr;
+ size_t total;
+ if (!plugin) return NULL;
+ if (count == 0) count = 1;
+ if (size == 0) size = 1;
+ if (count > ((size_t)-1) / size) return NULL;
+ total = count * size;
+ ptr = ecex_plugin_object_alloc(plugin, name, total);
+ if (!ptr) return NULL;
+ memset(ptr, 0, total);
+ return ptr;
+}
+
+int ecex_plugin_object_free(ecex_plugin_t *plugin, void *object) {
+ if (!plugin || !object) return ECEX_ERR;
+ for (size_t i = 0; i < plugin->object_count; ++i) {
+ if (plugin->objects[i].ptr != object) continue;
+ object_clear(&plugin->objects[i]);
+ if (i + 1 < plugin->object_count) {
+ memmove(&plugin->objects[i], &plugin->objects[i + 1],
+ (plugin->object_count - i - 1) * sizeof(plugin->objects[i]));
+ }
+ --plugin->object_count;
+ return ECEX_OK;
+ }
+ return ECEX_ERR;
+}
+
+int ecex_plugin_object_valid(ecex_plugin_t *plugin, void *object) {
+ return object_find(plugin, object) ? 1 : 0;
+}
+
+int ecex_plugin_object_i32_get(ecex_plugin_t *plugin,
+ void *object,
+ size_t byte_offset,
+ int fallback) {
+ ecex_plugin_object_t *entry = object_find(plugin, object);
+ int value;
+ if (!entry || !entry->ptr || byte_offset > entry->size ||
+ entry->size - byte_offset < sizeof(value)) {
+ return fallback;
+ }
+ memcpy(&value, (const char *)entry->ptr + byte_offset, sizeof(value));
+ return value;
+}
+
+int ecex_plugin_object_i32_set(ecex_plugin_t *plugin,
+ void *object,
+ size_t byte_offset,
+ int value) {
+ ecex_plugin_object_t *entry = object_find(plugin, object);
+ if (!entry || !entry->ptr || byte_offset > entry->size ||
+ entry->size - byte_offset < sizeof(value)) {
+ return ECEX_ERR;
+ }
+ memcpy((char *)entry->ptr + byte_offset, &value, sizeof(value));
+ return ECEX_OK;
+}
+
+void *ecex_plugin_object_ptr_get(ecex_plugin_t *plugin,
+ void *object,
+ size_t byte_offset) {
+ ecex_plugin_object_t *entry = object_find(plugin, object);
+ void *value = NULL;
+ if (!entry || !entry->ptr || byte_offset > entry->size ||
+ entry->size - byte_offset < sizeof(value)) {
+ return NULL;
+ }
+ memcpy(&value, (const char *)entry->ptr + byte_offset, sizeof(value));
+ return value;
+}
+
+int ecex_plugin_object_ptr_set(ecex_plugin_t *plugin,
+ void *object,
+ size_t byte_offset,
+ void *value) {
+ ecex_plugin_object_t *entry = object_find(plugin, object);
+ if (!entry || !entry->ptr || byte_offset > entry->size ||
+ entry->size - byte_offset < sizeof(value)) {
+ return ECEX_ERR;
+ }
+ memcpy((char *)entry->ptr + byte_offset, &value, sizeof(value));
+ return ECEX_OK;
+}
+
+int ecex_plugin_text_set(ecex_plugin_t *plugin, int id, const char *text, int len) {
+ ecex_plugin_text_t *entry;
+ char *copy;
+ size_t n;
+ if (!plugin || id < 0) return ECEX_ERR;
+ if (!text) text = "";
+ n = len < 0 ? strlen(text) : (size_t)len;
+ copy = malloc(n + 1);
+ if (!copy) return ECEX_ERR;
+ if (n) memcpy(copy, text, n);
+ copy[n] = '\0';
+
+ entry = text_find(plugin, id);
+ if (!entry) {
+ if (ECEX_GROW_ARRAY(plugin->texts,
+ plugin->text_count,
+ plugin->text_cap,
+ 32) != ECEX_OK) {
+ free(copy);
+ return ECEX_ERR;
+ }
+ entry = &plugin->texts[plugin->text_count++];
+ memset(entry, 0, sizeof(*entry));
+ entry->id = id;
+ }
+ free(entry->text);
+ entry->text = copy;
+ entry->len = n;
+ return ECEX_OK;
+}
+
+int ecex_plugin_text_set_from_buffer_title(ecex_plugin_t *plugin, int id, buffer_t *buffer) {
+ const char *title = NULL;
+ if (buffer) title = buffer->path ? buffer->path : buffer->name;
+ return ecex_plugin_text_set(plugin, id, title ? title : "(unnamed)", -1);
+}
+
+int ecex_plugin_text_free(ecex_plugin_t *plugin, int id) {
+ if (!plugin) return ECEX_ERR;
+ for (size_t i = 0; i < plugin->text_count; ++i) {
+ if (plugin->texts[i].id != id) continue;
+ text_clear(&plugin->texts[i]);
+ if (i + 1 < plugin->text_count) {
+ memmove(&plugin->texts[i], &plugin->texts[i + 1],
+ (plugin->text_count - i - 1) * sizeof(plugin->texts[i]));
+ }
+ --plugin->text_count;
+ return ECEX_OK;
+ }
+ return ECEX_ERR;
+}
+
+int ecex_plugin_text_free_all(ecex_plugin_t *plugin) {
+ if (!plugin) return ECEX_ERR;
+ for (size_t i = 0; i < plugin->text_count; ++i) text_clear(&plugin->texts[i]);
+ plugin->text_count = 0;
+ return ECEX_OK;
+}
+
+const char *ecex_plugin_text_get_drawable(ecex_t *ed, void *owner, int id) {
+ ecex_plugin_t *plugin = (ecex_plugin_t *)owner;
+ (void)ed;
+ ecex_plugin_text_t *entry = text_find(plugin, id);
+ return entry && entry->text ? entry->text : "";
+}
+
+int ecex_plugin_file_handler_register(ecex_plugin_t *plugin,
+ const char *extension,
+ ecex_file_handler_fn fn) {
+ ecex_plugin_file_handler_t *handler;
+ if (!plugin || !extension || !extension[0] || !fn) return ECEX_ERR;
+ for (size_t i = 0; i < plugin->file_handler_count; ++i) {
+ if (ascii_equal_ci(plugin->file_handlers[i].extension, extension)) {
+ plugin->file_handlers[i].fn = fn;
+ return ECEX_OK;
+ }
+ }
+ if (ECEX_GROW_ARRAY(plugin->file_handlers,
+ plugin->file_handler_count,
+ plugin->file_handler_cap,
+ 8) != ECEX_OK) {
+ return ECEX_ERR;
+ }
+ handler = &plugin->file_handlers[plugin->file_handler_count++];
+ memset(handler, 0, sizeof(*handler));
+ handler->extension = ecex_strdup(extension);
+ if (!handler->extension) {
+ --plugin->file_handler_count;
+ return ECEX_ERR;
+ }
+ handler->fn = fn;
+ return ECEX_OK;
+}
+
+int ecex_plugin_file_handlers_run(ecex_t *ed, buffer_t *buffer) {
+ const char *path;
+ const char *ext;
+ if (!ed || !ed->plugins || !buffer) return ECEX_ERR;
+ path = buffer->path ? buffer->path : buffer->name;
+ ext = path_extension(path);
+ if (!ext) return ECEX_OK;
+ for (size_t i = 0; i < ed->plugins->plugin_count; ++i) {
+ ecex_plugin_t *plugin = ed->plugins->plugins[i];
+ if (!plugin) continue;
+ for (size_t j = 0; j < plugin->file_handler_count; ++j) {
+ if (ascii_equal_ci(plugin->file_handlers[j].extension, ext)) {
+ return plugin->file_handlers[j].fn(ed, buffer);
+ }
+ }
+ }
+ return ECEX_OK;
+}