// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later #include "native.h" #include #include #include // Enchant 2's public ABI uses opaque broker/dictionary pointers. Resolve that // ABI at runtime so the dynamic linker never requires Enchant to start Fenrir. typedef struct _EnchantBroker EnchantBroker; typedef struct _EnchantDict EnchantDict; struct SpellDictionary { void *library; EnchantBroker *broker; EnchantDict *dictionary; EnchantBroker *(*broker_init)(void); void (*broker_free)(EnchantBroker *broker); EnchantDict *(*request_dict)(EnchantBroker *broker, const char *language); void (*free_dict)(EnchantBroker *broker, EnchantDict *dictionary); int (*check)(EnchantDict *dictionary, const char *word, ssize_t length); char **(*suggest)(EnchantDict *dictionary, const char *word, ssize_t length, size_t *count); void (*free_suggestions)(EnchantDict *dictionary, char **suggestions); }; void spell_dictionary_close(SpellDictionary *context) { if (!context) return; if (context->dictionary) context->free_dict(context->broker, context->dictionary); if (context->broker) context->broker_free(context->broker); if (context->library) dlclose(context->library); free(context); } SpellDictionary *spell_dictionary_open(const char *language, int *status) { *status = 3; SpellDictionary *context = calloc(1, sizeof(*context)); if (!context) return NULL; context->library = dlopen("libenchant-2.so.2", RTLD_NOW | RTLD_LOCAL); if (!context->library) { *status = 1; goto failed; } *status = 2; #define LOAD(field, symbol) \ context->field = dlsym(context->library, symbol); \ if (!context->field) goto failed LOAD(broker_init, "enchant_broker_init"); LOAD(broker_free, "enchant_broker_free"); LOAD(request_dict, "enchant_broker_request_dict"); LOAD(free_dict, "enchant_broker_free_dict"); LOAD(check, "enchant_dict_check"); LOAD(suggest, "enchant_dict_suggest"); LOAD(free_suggestions, "enchant_dict_free_string_list"); #undef LOAD *status = 3; context->broker = context->broker_init(); if (!context->broker) goto failed; *status = 4; context->dictionary = context->request_dict(context->broker, language); if (!context->dictionary) goto failed; *status = 0; return context; failed: spell_dictionary_close(context); return NULL; } int spell_dictionary_check(SpellDictionary *context, const char *word, size_t length) { return context->check(context->dictionary, word, (ssize_t)length); } char **spell_dictionary_suggest(SpellDictionary *context, const char *word, size_t length, size_t *count) { return context->suggest(context->dictionary, word, (ssize_t)length, count); } void spell_dictionary_free_suggestions(SpellDictionary *context, char **suggestions) { context->free_suggestions(context->dictionary, suggestions); }