From a15cb041654ae307add0b998b526c87c3f42bf5f Mon Sep 17 00:00:00 2001 From: David Moc Date: Tue, 2 Jun 2026 13:50:21 +0200 Subject: Add plugin hooks and mode plugins --- src/plugin.c | 613 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 613 insertions(+) create mode 100644 src/plugin.c (limited to 'src/plugin.c') 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 +#include +#include + +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; +} -- cgit v1.2.3