7#include "wamr_runtime/Wamr.h"
12#include "freertos/FreeRTOS.h"
13#include "freertos/task.h"
14#include "freertos/semphr.h"
48static const char*
TAG =
"PLG_MGR";
52constexpr uint32_t TICK_INTERVAL_MS = 50;
53constexpr uint32_t TICK_STACK_BYTES = 12288;
54constexpr UBaseType_t TICK_PRIORITY = 5;
58constexpr size_t kMaxTrapsPerDispatch = 8;
60void invokeDeinit(
Plugin& plugin)
63 if (!plugin.
callI(
"plugin_deinit", {}, &rc)) {
64 LOG_W(
TAG,
"plugin_deinit missing for %s", plugin.
id().c_str());
66 LOG_W(
TAG,
"plugin_deinit returned %ld for %s",
67 static_cast<long>(rc), plugin.
id().c_str());
74void applySleepInhibitor(
const Plugin& plugin,
bool on)
81 sm.addSleepInhibitor(plugin.
id().c_str());
86 sm.removeSleepInhibitor(plugin.
id().c_str());
93 explicit ScopedLock(SemaphoreHandle_t s, uint32_t timeout_ms = 1000) : m(s) {
94 if (m) taken = xSemaphoreTakeRecursive(m, pdMS_TO_TICKS(timeout_ms)) == pdTRUE;
96 ~ScopedLock() {
if (taken && m) xSemaphoreGiveRecursive(m); }
97 explicit operator bool()
const {
return taken; }
104 static PluginManager s;
108PluginManager::PluginManager() =
default;
109PluginManager::~PluginManager() =
default;
113 if (initialised_)
return true;
115 bh_log_set_verbose_level(BH_LOG_LEVEL_FATAL);
117 if (!cdc::wamr::init()) {
LOG_E(
TAG,
"WAMR init failed");
return false; }
121 call_mutex_ = xSemaphoreCreateRecursiveMutex();
122 if (!call_mutex_) {
LOG_E(
TAG,
"plugin mutex create failed");
return false; }
125 LOG_I(
TAG,
"PluginManager ready: %u plugin(s) installed",
126 static_cast<unsigned>(ids.size()));
128 if (!msg_index_mutex_) msg_index_mutex_ = xSemaphoreCreateMutex();
129 rebuildMessageIndex();
134 loadAutoloadPlugins();
144 ScopedLock l(
static_cast<SemaphoreHandle_t
>(call_mutex_));
145 for (
auto& p : background_) {
146 (void)p->callI(
"plugin_on_exit");
147 teardownPlugin(*p,
true);
153 vSemaphoreDelete(
static_cast<SemaphoreHandle_t
>(call_mutex_));
154 call_mutex_ =
nullptr;
159 initialised_ =
false;
167std::optional<PluginManifest>
172 if (!fp)
return std::nullopt;
173 std::fseek(fp.get(), 0, SEEK_END);
174 long n = std::ftell(fp.get());
175 if (n <= 0)
return std::nullopt;
176 std::fseek(fp.get(), 0, SEEK_SET);
177 std::string buf(
static_cast<size_t>(n),
'\0');
178 if (std::fread(buf.data(), 1, n, fp.get()) !=
static_cast<size_t>(n)) {
203 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
205 const std::string
id = id_ref;
208 LOG_W(
TAG,
"plugin %s is disabled",
id.c_str());
212 if (active_ && active_->id() ==
id) {
213 (void)active_->callI(
"plugin_on_enter");
215 pending_stop_.store(
false, std::memory_order_release);
220 (void)active_->callI(
"plugin_on_exit");
221 if (active_->manifest().capabilities.background && active_->residentRequested()) {
222 background_.push_back(std::move(active_));
224 teardownPlugin(*active_,
true);
231 auto it = std::find_if(background_.begin(), background_.end(),
232 [&](
const std::unique_ptr<Plugin>& p) {
233 return p && p->id() == id;
235 if (it != background_.end()) {
236 auto plugin = std::move(*it);
237 background_.erase(it);
240 int32_t enter_rc = 0;
241 if (!plugin->callI(
"plugin_on_enter", {}, &enter_rc)) {
242 LOG_E(
TAG,
"plugin_on_enter missing for %s",
id.c_str());
243 background_.push_back(std::move(plugin));
246 active_ = std::move(plugin);
248 pending_stop_.store(
false, std::memory_order_release);
249 LOG_I(
TAG,
"plugin %s promoted bg->fg",
id.c_str());
255 LOG_W(
TAG,
"manifest missing/invalid for %s",
id.c_str());
262 LOG_W(
TAG,
"capability check failed for %s: %s",
263 id.c_str(), check.detail.c_str());
267 auto plugin = std::make_unique<Plugin>();
268 if (!plugin->load(
id, mf)) {
269 LOG_E(
TAG,
"Plugin::load failed for %s",
id.c_str());
272 plugin->loadLangOverlay();
275 if (!plugin->callI(
"plugin_init", {}, &init_rc) || init_rc != 0) {
276 LOG_E(
TAG,
"plugin_init failed for %s (rc=%ld)",
id.c_str(),
277 static_cast<long>(init_rc));
278 teardownPlugin(*plugin, !plugin->lastCallTrapped());
282 std::string failed_name, on_fail;
285 LOG_E(
TAG,
"prerequisite '%s' aborted start of %s",
286 failed_name.c_str(),
id.c_str());
287 teardownPlugin(*plugin,
true);
291 LOG_W(
TAG,
"prerequisite '%s' soft-failed for %s, continuing",
292 failed_name.c_str(),
id.c_str());
296 char loading_msg[80];
298 const auto& meta = plugin->manifest().i18n_meta;
299 const char* display =
id.c_str();
300 auto it = meta.find(
"name");
301 if (it != meta.end() && !it->second.by_lang.empty()) {
302 display = it->second.by_lang.begin()->second.c_str();
304 std::snprintf(loading_msg,
sizeof(loading_msg),
"%s\n%s",
311 auto hide_loading_if_top = []() {
313 if (vs.getModal() == &s_loading_toast) {
319 int32_t enter_rc = 0;
320 if (!plugin->callI(
"plugin_on_enter", {}, &enter_rc)) {
321 hide_loading_if_top();
322 if (plugin->lastCallTrapped()) {
323 LOG_E(
TAG,
"plugin_on_enter trapped for %s: %s",
id.c_str(),
324 plugin->lastTrapMessage());
326 LOG_E(
TAG,
"plugin_on_enter export missing for %s",
id.c_str());
328 teardownPlugin(*plugin, !plugin->lastCallTrapped());
331 hide_loading_if_top();
333 LOG_W(
TAG,
"plugin_on_enter returned %ld for %s",
334 static_cast<long>(enter_rc),
id.c_str());
337 active_ = std::move(plugin);
339 pending_stop_.store(
false, std::memory_order_release);
340 applySleepInhibitor(*active_,
true);
341 LOG_I(
TAG,
"plugin %s started",
id.c_str());
347 pending_stop_.store(
true, std::memory_order_release);
352 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
353 if (!lock)
return false;
354 if (!active_)
return false;
355 LOG_I(
TAG,
"stopping plugin %s", active_->id().c_str());
356 (void)active_->callI(
"plugin_on_exit");
369 if (active_->manifest().capabilities.background && active_->residentRequested()) {
370 background_.push_back(std::move(active_));
375 teardownPlugin(*active_,
true);
382 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
383 if (!lock)
return false;
384 if (active_ && active_->id() ==
id) {
385 LOG_I(
TAG,
"unloading active plugin %s from RAM",
id.c_str());
386 (void)active_->callI(
"plugin_on_exit");
391 teardownPlugin(*active_,
true);
395 auto it = std::find_if(background_.begin(), background_.end(),
396 [&](
const std::unique_ptr<Plugin>& p) {
397 return p && p->id() == id;
399 if (it == background_.end())
return false;
400 LOG_I(
TAG,
"unloading background plugin %s from RAM",
id.c_str());
401 teardownPlugin(**it,
true);
402 background_.erase(it);
414 LOG_I(
TAG,
"uninstalled plugin %s",
id.c_str());
420 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
423 LOG_I(
TAG,
"unloading active plugin %s from RAM", active_->id().c_str());
424 (void)active_->callI(
"plugin_on_exit");
429 teardownPlugin(*active_,
true);
432 for (
auto& p : background_) {
434 LOG_I(
TAG,
"unloading background plugin %s from RAM", p->id().c_str());
435 teardownPlugin(*p,
true);
441void PluginManager::teardownPlugin(
Plugin& p,
bool runWasmDeinit)
443 if (runWasmDeinit) invokeDeinit(p);
444 applySleepInhibitor(p,
false);
458void PluginManager::handleTrap(Plugin& p,
const char* fn)
460 LOG_E(
TAG,
"==================== PLUGIN TRAP ====================");
461 LOG_E(
TAG,
"plugin '%s' trapped in %s", p.id().c_str(), fn);
462 LOG_E(
TAG,
" reason : %s", p.lastTrapMessage());
463 LOG_E(
TAG,
" action : force-unload + release all held resources");
465 if (&p == active_.get()) {
467 while (vs.depth() > plugin_base_depth_ && vs.depth() > 1) vs.pop();
469 teardownPlugin(p,
false);
471 LOG_E(
TAG,
"=====================================================");
474 for (
auto it = background_.begin(); it != background_.end(); ++it) {
475 if (it->get() == &p) {
476 teardownPlugin(**it,
false);
477 background_.erase(it);
478 LOG_E(
TAG,
"=====================================================");
482 LOG_E(
TAG,
"=====================================================");
490 if (!manifest || !manifest->capabilities.background)
return false;
499 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
500 if (!lock)
return false;
501 if (!loadIntoBackground(
id, *manifest))
return false;
502 LOG_I(
TAG,
"background plugin %s reloaded",
id.c_str());
506bool PluginManager::loadIntoBackground(
const std::string&
id,
const PluginManifest& mf)
509 LOG_I(
TAG,
"bg load %s skipped: disabled",
id.c_str());
513 auto plugin = std::make_unique<Plugin>();
514 if (!plugin->load(
id, mf)) {
515 LOG_E(
TAG,
"bg load %s: load failed",
id.c_str());
518 plugin->loadLangOverlay();
520 if (!plugin->callI(
"plugin_init", {}, &init_rc) || init_rc != 0) {
521 LOG_E(
TAG,
"bg load %s: plugin_init failed (rc=%ld)",
id.c_str(),
522 static_cast<long>(init_rc));
523 teardownPlugin(*plugin, !plugin->lastCallTrapped());
526 std::string failed_name, on_fail;
529 LOG_E(
TAG,
"bg load %s: prereq '%s' aborted",
id.c_str(), failed_name.c_str());
530 teardownPlugin(*plugin,
true);
533 background_.push_back(std::move(plugin));
534 applySleepInhibitor(*background_.back(),
true);
538void PluginManager::loadAutoloadPlugins()
541 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
543 for (
const auto&
id : ids) {
547 LOG_I(
TAG,
"autoload %s skipped: disabled",
id.c_str());
554 LOG_W(
TAG,
"autoload %s rejected: %s",
id.c_str(), check.detail.c_str());
557 if (loadIntoBackground(
id, *mf)) {
564 if (!background_.empty() && background_.back()->residentRequested()) {
565 LOG_I(
TAG,
"autoloaded plugin %s (resident)",
id.c_str());
567 LOG_I(
TAG,
"autoload %s: no set_resident(true), not staying resident",
572 LOG_W(
TAG,
"autoload of %s failed",
id.c_str());
577void PluginManager::rebuildMessageIndex()
579 std::vector<std::string> mimes, mids;
580 std::vector<std::string> feats, fids;
590 bool duplicate =
false;
591 for (
size_t i = 0; i < feats.size(); ++i) {
592 if (feats[i] == feat) { duplicate =
true;
593 LOG_W(
TAG,
"feature '%s' already provided by %s, ignoring %s",
594 feat.c_str(), fids[i].c_str(),
id.c_str());
598 if (duplicate)
continue;
599 feats.push_back(feat);
603 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
604 if (m) xSemaphoreTake(m, portMAX_DELAY);
605 msg_index_mime_.swap(mimes);
606 msg_index_id_.swap(mids);
607 feat_index_name_.swap(feats);
608 feat_index_id_.swap(fids);
609 if (m) xSemaphoreGive(m);
612void PluginManager::maybeRefreshMessageIndex()
614 uint32_t now =
static_cast<uint32_t
>(esp_timer_get_time() / 1000);
615 if (now - last_index_refresh_ms_ < 2000)
return;
616 last_index_refresh_ms_ = now;
619 if (sig == installed_sig_)
return;
620 installed_sig_ = sig;
621 rebuildMessageIndex();
626 if (!mime)
return false;
627 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
630 if (m && xSemaphoreTake(m, pdMS_TO_TICKS(20)) != pdTRUE)
return false;
632 for (
const auto& mt : msg_index_mime_) {
633 if (mt == mime) { found =
true;
break; }
635 if (m) xSemaphoreGive(m);
641 if (!feature)
return false;
642 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
643 if (m && xSemaphoreTake(m, pdMS_TO_TICKS(20)) != pdTRUE)
return false;
645 for (
const auto&
name : feat_index_name_) {
646 if (
name == feature) { found =
true;
break; }
648 if (m) xSemaphoreGive(m);
654 if (!feature)
return {};
656 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
661 if (m && xSemaphoreTake(m, pdMS_TO_TICKS(20)) != pdTRUE)
return {};
662 for (
size_t i = 0; i < feat_index_name_.size(); ++i) {
663 if (feat_index_name_[i] == feature) {
id = feat_index_id_[i];
break; }
665 if (m) xSemaphoreGive(m);
671 if (!mime)
return false;
674 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
675 if (m) xSemaphoreTake(m, portMAX_DELAY);
676 for (
size_t i = 0; i < msg_index_mime_.size(); ++i) {
677 if (msg_index_mime_[i] == mime) {
id = msg_index_id_[i];
break; }
679 if (m) xSemaphoreGive(m);
681 if (
id.empty())
return false;
683 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
684 if (!lock)
return false;
688 if (!mf)
return false;
691 LOG_W(
TAG,
"msg activate %s rejected: %s",
id.c_str(), check.detail.c_str());
694 return loadIntoBackground(
id, *mf);
701 const uint8_t copy = n < max ? n : max;
704 for (uint8_t i = 0; i < copy; ++i) {
709 const char* label =
nullptr;
711 label = p->
trKey(regs[i].label_key);
715 auto it = strings.find(regs[i].label_key);
716 if (it != strings.end()) {
717 auto pick = [&](
const std::string& l) ->
const char* {
718 auto lit = it->second.by_lang.find(l);
719 return lit != it->second.by_lang.end() ? lit->second.c_str() :
nullptr;
722 if (!label) label = pick(
"en");
723 if (!label && !it->second.by_lang.empty())
724 label = it->second.by_lang.begin()->second.c_str();
729 std::strncpy(out[i].label, label,
sizeof(out[i].label) - 1);
730 out[i].
label[
sizeof(out[i].
label) - 1] =
'\0';
745 if (active_ && active_->id() ==
id)
return true;
746 for (
const auto& p : background_)
if (p->id() ==
id)
return true;
752 for (
const auto& p : background_)
if (p->id() ==
id)
return true;
758 return active_ && active_->manifest().capabilities.background;
763 return active_ && active_->manifest().capabilities.prevent_sleep;
768 return !background_.empty();
773 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
775 if (!active_)
return;
777 (void)active_->callI(
"plugin_on_button",
778 {static_cast<int32_t>(button_code)}, &rc);
779 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_button");
784 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
786 if (!active_)
return;
788 (void)active_->callI(
"plugin_on_action",
789 {static_cast<int32_t>(action_id),
790 static_cast<int32_t>(idx),
791 static_cast<int32_t>(user_data)}, &rc);
792 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_action");
796 uint32_t idx, uint32_t user_data)
799 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
802 bool found = (active_.get() == plugin);
804 for (
auto& p : background_)
if (p.get() == plugin) { found =
true;
break; }
809 (void)plugin->
callI(
"plugin_on_action",
810 {static_cast<int32_t>(action_id),
811 static_cast<int32_t>(idx),
812 static_cast<int32_t>(user_data)}, &rc);
813 if (plugin->
lastCallTrapped()) handleTrap(*plugin,
"plugin_on_action");
818 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
821 const int32_t hi =
static_cast<int32_t
>(uptime_ms >> 32);
822 const int32_t lo =
static_cast<int32_t
>(uptime_ms & 0xFFFFFFFFu);
824 (void)active_->callI(
"plugin_on_tick", {lo, hi}, &rc);
825 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_tick");
827 Plugin* trapped[kMaxTrapsPerDispatch];
828 size_t n_trapped = 0;
829 for (
auto& p : background_) {
830 (void)p->callI(
"plugin_on_tick", {lo, hi}, &rc);
831 if (p->lastCallTrapped() && n_trapped < kMaxTrapsPerDispatch)
832 trapped[n_trapped++] = p.get();
834 for (
size_t i = 0; i < n_trapped; ++i) handleTrap(*trapped[i],
"plugin_on_tick");
843 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
847 (void)active_->callI(
"plugin_on_event",
848 {static_cast<int32_t>(event_type),
849 static_cast<int32_t>(value)}, &rc);
850 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_event");
852 Plugin* trapped[kMaxTrapsPerDispatch];
853 size_t n_trapped = 0;
854 for (
auto& p : background_) {
855 (void)p->callI(
"plugin_on_event",
856 {static_cast<int32_t>(event_type),
857 static_cast<int32_t>(value)}, &rc);
858 if (p->lastCallTrapped() && n_trapped < kMaxTrapsPerDispatch)
859 trapped[n_trapped++] = p.get();
861 for (
size_t i = 0; i < n_trapped; ++i) handleTrap(*trapped[i],
"plugin_on_event");
866 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
867 if (!lock)
return false;
869 Plugin* target = (active_ && active_->id() ==
id) ? active_.get() :
nullptr;
871 for (
auto& p : background_)
if (p->id() ==
id) { target = p.get();
break; }
873 if (!target || !target->
hasExport(
"plugin_on_cmd"))
return false;
875 pending_cmd_.assign(cmd ? cmd :
"", len);
877 (void)target->
callI(
"plugin_on_cmd", {static_cast<int32_t>(len)}, &rc);
878 pending_cmd_.clear();
886 size_t n = pending_cmd_.size();
887 if (n >= out_size) n = out_size - 1;
888 std::memcpy(out, pending_cmd_.data(), n);
890 pending_cmd_.clear();
891 return static_cast<int>(n);
896 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
898 if (active_) {
if (!visitor(*active_))
return; }
899 for (
auto& p : background_) {
900 if (!visitor(*p))
return;
906 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
908 if (active_) active_->loadLangOverlay();
909 for (
auto& p : background_) p->loadLangOverlay();
912void PluginManager::tickTaskTrampoline(
void* arg)
915 vTaskDelete(
nullptr);
918void PluginManager::tickTaskLoop()
920 TickType_t last = xTaskGetTickCount();
921 while (!tick_stop_) {
922 vTaskDelayUntil(&last, pdMS_TO_TICKS(TICK_INTERVAL_MS));
923 if (tick_stop_)
break;
924 maybeRefreshMessageIndex();
925 if (pending_stop_.exchange(
false, std::memory_order_acq_rel)) {
936void PluginManager::startTickTask()
938 if (tick_task_)
return;
940 TaskHandle_t handle =
nullptr;
941 if (xTaskCreate(&PluginManager::tickTaskTrampoline,
942 "plg_tick", TICK_STACK_BYTES,
this,
943 TICK_PRIORITY, &handle) != pdPASS) {
944 LOG_E(
TAG,
"failed to spawn plg_tick task");
950void PluginManager::stopTickTask()
952 if (!tick_task_)
return;
956 vTaskDelay(pdMS_TO_TICKS(TICK_INTERVAL_MS * 2));
957 tick_task_ =
nullptr;
Load-time validation of plugin capabilities + manifest sanity.
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
Internal registry of plugin lockscreen quick-actions.
Main-menu entry "Plugins" - lists all installed WASM plugins.
void plg_ext_feature_pump(void)
void plg_ble_on_unload(void *plugin)
void plg_msg_on_unload(void *plugin)
void plg_http_on_unload(void *plugin)
void plg_socket_on_unload(void *plugin)
void plg_gpio_on_unload(void *plugin)
void plg_surface_on_unload(void *plugin)
void plg_ext_feature_on_unload(void *plugin)
void plg_net_on_unload(void *plugin)
Discovers, loads, runs and unloads WASM plugins on the badge.
Singleton that owns plugin-pushed UI views (lists, confirms, inputs).
Owned WAMR module instance + per-plugin state.
Walks the prerequisites list of a plugin manifest before plugin_on_enter.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
Registers the host API as WAMR native imports under module "cdc".
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
static CapabilityCheckResult validate(const PluginManifest &manifest)
static PluginListView * active() noexcept
Currently-mounted PluginListView instance, or nullptr if none.
bool dispatchCmd(const std::string &id, const char *cmd, size_t len)
bool activateForMessageType(const char *mime)
Load + start (headless) the installed plugin that declares this MIME type, so its message handler bec...
bool activePluginPreventsSleep() const
uint8_t getLockscreenItems(LockscreenItem *out, uint8_t max) const
Snapshot of all plugin lockscreen items. Returns the number written.
std::string activePluginId() const
void dispatchAction(uint32_t action_id, uint32_t idx, uint32_t user_data)
int consumeCmd(char *out, size_t out_size)
void requestStopActivePlugin()
bool hasBackgroundPlugin() const noexcept
True if at least one plugin is currently resident in the background slot.
bool isLoaded(const std::string &id) const
True if a plugin with id is loaded in RAM (foreground or background).
void dispatchButton(uint32_t button_code)
bool hasActivePlugin() const noexcept
bool isRunningInBackground(const std::string &id) const
True if a plugin with id is currently resident in the background slot.
void forEachPlugin(const std::function< bool(Plugin &)> &visitor)
Iterate foreground + background plugins. Visitor returns false to stop.
std::vector< std::string > listInstalledIds() const
static PluginManager & instance() noexcept
bool unloadFromRam(const std::string &id)
void dispatchActionTo(Plugin *plugin, uint32_t action_id, uint32_t idx, uint32_t user_data)
bool setPluginDisabled(const std::string &id, bool disabled)
bool activePluginIsBackground() const
void dispatchTick(uint64_t uptime_ms)
bool uninstallPlugin(const std::string &id)
void triggerLockscreenItem(const LockscreenItem &item)
Fire plugin_on_action(item.action_id, 0, 0) on the owning plugin.
bool reloadBackgroundPlugin(const std::string &id)
bool featureInstalled(const char *feature) const
True if any installed plugin's manifest provides this external feature. Reads the cached index (same ...
bool messageTypeInstalled(const char *mime) const
True if any installed plugin's manifest declares this MIME type for message transfer....
bool isPluginDisabled(const std::string &id) const
void reloadActiveLangOverlay()
std::string featureProviderId(const char *feature) const
Installed plugin id providing this external feature, or empty.
std::optional< PluginManifest > getManifest(const std::string &id) const
void dispatchEventAll(uint32_t event_type, uint32_t value)
StartResult startPlugin(const std::string &id)
static bool mount()
Mount the plugins partition. Auto-formats if empty.
static std::string langPath(const std::string &id)
Returns the full VFS path of <id>.lang (translation overlay).
static void unmount()
Unmount the plugins partition (rarely used; mostly tests).
static std::string aotPath(const std::string &id)
Returns the full VFS path of <id>.aot.
static bool isDisabled(const std::string &id)
True when the plugin has a persistent disabled marker.
static bool setDisabled(const std::string &id, bool disabled)
Create or remove the persistent disabled marker for a plugin.
static std::string disabledPath(const std::string &id)
Returns the full VFS path of <id>.disabled.
static std::vector< std::string > listPluginIds()
Discover all installed plugin ids. A plugin is recognised by the presence of both <id>....
static std::string wasmPath(const std::string &id)
Returns the full VFS path of <id>.wasm.
static std::string metaPath(const std::string &id)
Returns the full VFS path of <id>.meta.
void resetForPluginStop()
static PluginUiState & instance() noexcept
bool hasExport(const char *name) const
const char * trKey(const char *key) const noexcept
Look up a plugin-local translation key in the loaded overlay.
bool lastCallTrapped() const noexcept
bool callI(const char *name, std::initializer_list< int32_t > args={}, int32_t *out_i32=nullptr)
Call an exported i32(i32...)->i32 function by name.
void unload() noexcept
Destroy WAMR instance + free bytecode buffer. Idempotent.
const std::string & id() const noexcept
const PluginManifest & manifest() const noexcept
static PrereqResult walk(Plugin &plugin, std::string &out_failed_name, std::string &out_on_fail)
Walk the plugin's prerequisite list in order. Marks acquired resources on the Plugin so release() can...
static void release(Plugin &plugin)
Release every resource the plugin acquired during walk(), in reverse order of acquisition.
const std::string & getLanguageCode() const
Current language code (lower-case ISO-639-1, e.g. "en", "de").
static I18n & instance()
Singleton accessor.
static SleepManager & instance()
Returns singleton sleep manager instance.
void init(const char *message, Icon icon=Icon::NONE, uint16_t durationMs=1500, bool dismissible=true)
Initializes toast message content and timing behavior.
void render(bool synchronous=false)
Render current view (and modal if present) and flush to display.
static ViewStack & instance()
Returns singleton view-stack instance.
void showModal(IView *modal)
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_ERR_INVALID_ARG
void plg_ble_on_unload(void *plugin)
void plg_ext_feature_pump(void)
void plg_ext_feature_on_unload(void *plugin)
void plg_gpio_on_unload(void *plugin)
void plg_http_on_unload(void *plugin)
void plg_msg_on_unload(void *plugin)
void plg_net_on_unload(void *plugin)
void plg_socket_on_unload(void *plugin)
void plg_surface_on_unload(void *plugin)
FilePtr openFile(const char *path, const char *mode) noexcept
Open a FILE* and wrap it in a FilePtr.
void unregister_host_imports()
Unregister the imports (called from PluginManager::deinit()).
uint8_t collectLockscreenItems(LockscreenRegistration *out, uint8_t max)
void clearLockscreenRegistrationFor(void *plugin)
bool register_host_imports()
Register the "cdc" import namespace with WAMR.
const char * tr(const char *key)
Look up a translation by string key.
bool autoload
Start this plugin as a resident background instance at badge boot. Plugins without this flag stay unl...
std::vector< std::string > message_types
std::vector< std::string > provides
static bool parse(const char *json, size_t len, PluginManifest &out)
Parse meta.json content. Returns false on schema errors.
PluginCapabilities capabilities
std::map< std::string, LocalizedString > i18n_strings