7#include "wamr_runtime/Wamr.h"
12#include "freertos/FreeRTOS.h"
13#include "freertos/task.h"
14#include "freertos/semphr.h"
43static const char*
TAG =
"PLG_MGR";
47constexpr uint32_t TICK_INTERVAL_MS = 50;
48constexpr uint32_t TICK_STACK_BYTES = 12288;
49constexpr UBaseType_t TICK_PRIORITY = 5;
53constexpr size_t kMaxTrapsPerDispatch = 8;
55void invokeDeinit(
Plugin& plugin)
58 if (!plugin.
callI(
"plugin_deinit", {}, &rc)) {
59 LOG_W(
TAG,
"plugin_deinit missing for %s", plugin.
id().c_str());
61 LOG_W(
TAG,
"plugin_deinit returned %ld for %s",
62 static_cast<long>(rc), plugin.
id().c_str());
69void applySleepInhibitor(
const Plugin& plugin,
bool on)
76 sm.addSleepInhibitor(plugin.
id().c_str());
81 sm.removeSleepInhibitor(plugin.
id().c_str());
88 explicit ScopedLock(SemaphoreHandle_t s, uint32_t timeout_ms = 1000) : m(s) {
89 if (m) taken = xSemaphoreTakeRecursive(m, pdMS_TO_TICKS(timeout_ms)) == pdTRUE;
91 ~ScopedLock() {
if (taken && m) xSemaphoreGiveRecursive(m); }
92 explicit operator bool()
const {
return taken; }
99 static PluginManager s;
103PluginManager::PluginManager() =
default;
104PluginManager::~PluginManager() =
default;
108 if (initialised_)
return true;
110 bh_log_set_verbose_level(BH_LOG_LEVEL_FATAL);
112 if (!cdc::wamr::init()) {
LOG_E(
TAG,
"WAMR init failed");
return false; }
116 call_mutex_ = xSemaphoreCreateRecursiveMutex();
117 if (!call_mutex_) {
LOG_E(
TAG,
"plugin mutex create failed");
return false; }
120 LOG_I(
TAG,
"PluginManager ready: %u plugin(s) installed",
121 static_cast<unsigned>(ids.size()));
123 if (!msg_index_mutex_) msg_index_mutex_ = xSemaphoreCreateMutex();
124 rebuildMessageIndex();
129 loadAutoloadPlugins();
139 ScopedLock l(
static_cast<SemaphoreHandle_t
>(call_mutex_));
140 for (
auto& p : background_) {
141 (void)p->callI(
"plugin_on_exit");
142 teardownPlugin(*p,
true);
148 vSemaphoreDelete(
static_cast<SemaphoreHandle_t
>(call_mutex_));
149 call_mutex_ =
nullptr;
154 initialised_ =
false;
162std::optional<PluginManifest>
167 if (!fp)
return std::nullopt;
168 std::fseek(fp.get(), 0, SEEK_END);
169 long n = std::ftell(fp.get());
170 if (n <= 0)
return std::nullopt;
171 std::fseek(fp.get(), 0, SEEK_SET);
172 std::string buf(
static_cast<size_t>(n),
'\0');
173 if (std::fread(buf.data(), 1, n, fp.get()) !=
static_cast<size_t>(n)) {
198 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
200 const std::string
id = id_ref;
203 LOG_W(
TAG,
"plugin %s is disabled",
id.c_str());
207 if (active_ && active_->id() ==
id) {
208 (void)active_->callI(
"plugin_on_enter");
210 pending_stop_.store(
false, std::memory_order_release);
215 (void)active_->callI(
"plugin_on_exit");
216 if (active_->manifest().capabilities.background) {
217 background_.push_back(std::move(active_));
219 teardownPlugin(*active_,
true);
226 auto it = std::find_if(background_.begin(), background_.end(),
227 [&](
const std::unique_ptr<Plugin>& p) {
228 return p && p->id() == id;
230 if (it != background_.end()) {
231 auto plugin = std::move(*it);
232 background_.erase(it);
235 int32_t enter_rc = 0;
236 if (!plugin->callI(
"plugin_on_enter", {}, &enter_rc)) {
237 LOG_E(
TAG,
"plugin_on_enter missing for %s",
id.c_str());
238 background_.push_back(std::move(plugin));
241 active_ = std::move(plugin);
243 pending_stop_.store(
false, std::memory_order_release);
244 LOG_I(
TAG,
"plugin %s promoted bg->fg",
id.c_str());
250 LOG_W(
TAG,
"manifest missing/invalid for %s",
id.c_str());
257 LOG_W(
TAG,
"capability check failed for %s: %s",
258 id.c_str(), check.detail.c_str());
262 auto plugin = std::make_unique<Plugin>();
263 if (!plugin->load(
id, mf)) {
264 LOG_E(
TAG,
"Plugin::load failed for %s",
id.c_str());
267 plugin->loadLangOverlay();
270 if (!plugin->callI(
"plugin_init", {}, &init_rc) || init_rc != 0) {
271 LOG_E(
TAG,
"plugin_init failed for %s (rc=%ld)",
id.c_str(),
272 static_cast<long>(init_rc));
273 teardownPlugin(*plugin, !plugin->lastCallTrapped());
277 std::string failed_name, on_fail;
280 LOG_E(
TAG,
"prerequisite '%s' aborted start of %s",
281 failed_name.c_str(),
id.c_str());
282 teardownPlugin(*plugin,
true);
286 LOG_W(
TAG,
"prerequisite '%s' soft-failed for %s, continuing",
287 failed_name.c_str(),
id.c_str());
291 char loading_msg[80];
293 const auto& meta = plugin->manifest().i18n_meta;
294 const char* display =
id.c_str();
295 auto it = meta.find(
"name");
296 if (it != meta.end() && !it->second.by_lang.empty()) {
297 display = it->second.by_lang.begin()->second.c_str();
299 std::snprintf(loading_msg,
sizeof(loading_msg),
"%s\n%s",
306 auto hide_loading_if_top = []() {
308 if (vs.getModal() == &s_loading_toast) {
314 int32_t enter_rc = 0;
315 if (!plugin->callI(
"plugin_on_enter", {}, &enter_rc)) {
316 hide_loading_if_top();
317 if (plugin->lastCallTrapped()) {
318 LOG_E(
TAG,
"plugin_on_enter trapped for %s: %s",
id.c_str(),
319 plugin->lastTrapMessage());
321 LOG_E(
TAG,
"plugin_on_enter export missing for %s",
id.c_str());
323 teardownPlugin(*plugin, !plugin->lastCallTrapped());
326 hide_loading_if_top();
328 LOG_W(
TAG,
"plugin_on_enter returned %ld for %s",
329 static_cast<long>(enter_rc),
id.c_str());
332 active_ = std::move(plugin);
334 pending_stop_.store(
false, std::memory_order_release);
335 applySleepInhibitor(*active_,
true);
336 LOG_I(
TAG,
"plugin %s started",
id.c_str());
342 pending_stop_.store(
true, std::memory_order_release);
347 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
348 if (!lock)
return false;
349 if (!active_)
return false;
350 LOG_I(
TAG,
"stopping plugin %s", active_->id().c_str());
351 (void)active_->callI(
"plugin_on_exit");
364 if (active_->manifest().capabilities.background) {
365 background_.push_back(std::move(active_));
370 teardownPlugin(*active_,
true);
377 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
378 if (!lock)
return false;
379 if (active_ && active_->id() ==
id) {
380 LOG_I(
TAG,
"unloading active plugin %s from RAM",
id.c_str());
381 (void)active_->callI(
"plugin_on_exit");
386 teardownPlugin(*active_,
true);
390 auto it = std::find_if(background_.begin(), background_.end(),
391 [&](
const std::unique_ptr<Plugin>& p) {
392 return p && p->id() == id;
394 if (it == background_.end())
return false;
395 LOG_I(
TAG,
"unloading background plugin %s from RAM",
id.c_str());
396 teardownPlugin(**it,
true);
397 background_.erase(it);
403 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
406 LOG_I(
TAG,
"unloading active plugin %s from RAM", active_->id().c_str());
407 (void)active_->callI(
"plugin_on_exit");
412 teardownPlugin(*active_,
true);
415 for (
auto& p : background_) {
417 LOG_I(
TAG,
"unloading background plugin %s from RAM", p->id().c_str());
418 teardownPlugin(*p,
true);
424void PluginManager::teardownPlugin(
Plugin& p,
bool runWasmDeinit)
426 if (runWasmDeinit) invokeDeinit(p);
427 applySleepInhibitor(p,
false);
438void PluginManager::handleTrap(Plugin& p,
const char* fn)
440 LOG_E(
TAG,
"==================== PLUGIN TRAP ====================");
441 LOG_E(
TAG,
"plugin '%s' trapped in %s", p.id().c_str(), fn);
442 LOG_E(
TAG,
" reason : %s", p.lastTrapMessage());
443 LOG_E(
TAG,
" action : force-unload + release all held resources");
445 if (&p == active_.get()) {
447 while (vs.depth() > plugin_base_depth_ && vs.depth() > 1) vs.pop();
449 teardownPlugin(p,
false);
451 LOG_E(
TAG,
"=====================================================");
454 for (
auto it = background_.begin(); it != background_.end(); ++it) {
455 if (it->get() == &p) {
456 teardownPlugin(**it,
false);
457 background_.erase(it);
458 LOG_E(
TAG,
"=====================================================");
462 LOG_E(
TAG,
"=====================================================");
470 if (!manifest || !manifest->capabilities.background)
return false;
479 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
480 if (!lock)
return false;
481 if (!loadIntoBackground(
id, *manifest))
return false;
482 LOG_I(
TAG,
"background plugin %s reloaded",
id.c_str());
486bool PluginManager::loadIntoBackground(
const std::string&
id,
const PluginManifest& mf)
489 LOG_I(
TAG,
"bg load %s skipped: disabled",
id.c_str());
493 auto plugin = std::make_unique<Plugin>();
494 if (!plugin->load(
id, mf)) {
495 LOG_E(
TAG,
"bg load %s: load failed",
id.c_str());
498 plugin->loadLangOverlay();
500 if (!plugin->callI(
"plugin_init", {}, &init_rc) || init_rc != 0) {
501 LOG_E(
TAG,
"bg load %s: plugin_init failed (rc=%ld)",
id.c_str(),
502 static_cast<long>(init_rc));
503 teardownPlugin(*plugin, !plugin->lastCallTrapped());
506 std::string failed_name, on_fail;
509 LOG_E(
TAG,
"bg load %s: prereq '%s' aborted",
id.c_str(), failed_name.c_str());
510 teardownPlugin(*plugin,
true);
513 background_.push_back(std::move(plugin));
514 applySleepInhibitor(*background_.back(),
true);
518void PluginManager::loadAutoloadPlugins()
521 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
523 for (
const auto&
id : ids) {
527 LOG_I(
TAG,
"autoload %s skipped: disabled",
id.c_str());
534 LOG_W(
TAG,
"autoload %s rejected: %s",
id.c_str(), check.detail.c_str());
537 if (loadIntoBackground(
id, *mf)) {
538 LOG_I(
TAG,
"autoloaded plugin %s (resident)",
id.c_str());
540 LOG_W(
TAG,
"autoload of %s failed",
id.c_str());
545void PluginManager::rebuildMessageIndex()
547 std::vector<std::string> mimes, mids;
557 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
558 if (m) xSemaphoreTake(m, portMAX_DELAY);
559 msg_index_mime_.swap(mimes);
560 msg_index_id_.swap(mids);
561 if (m) xSemaphoreGive(m);
564void PluginManager::maybeRefreshMessageIndex()
566 uint32_t now =
static_cast<uint32_t
>(esp_timer_get_time() / 1000);
567 if (now - last_index_refresh_ms_ < 2000)
return;
568 last_index_refresh_ms_ = now;
571 if (sig == installed_sig_)
return;
572 installed_sig_ = sig;
573 rebuildMessageIndex();
578 if (!mime)
return false;
579 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
582 if (m && xSemaphoreTake(m, pdMS_TO_TICKS(20)) != pdTRUE)
return false;
584 for (
const auto& mt : msg_index_mime_) {
585 if (mt == mime) { found =
true;
break; }
587 if (m) xSemaphoreGive(m);
593 if (!mime)
return false;
596 auto* m =
static_cast<SemaphoreHandle_t
>(msg_index_mutex_);
597 if (m) xSemaphoreTake(m, portMAX_DELAY);
598 for (
size_t i = 0; i < msg_index_mime_.size(); ++i) {
599 if (msg_index_mime_[i] == mime) {
id = msg_index_id_[i];
break; }
601 if (m) xSemaphoreGive(m);
603 if (
id.empty())
return false;
605 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
606 if (!lock)
return false;
610 if (!mf)
return false;
613 LOG_W(
TAG,
"msg activate %s rejected: %s",
id.c_str(), check.detail.c_str());
616 return loadIntoBackground(
id, *mf);
623 const uint8_t copy = n < max ? n : max;
626 for (uint8_t i = 0; i < copy; ++i) {
631 const char* label =
nullptr;
633 label = p->
trKey(regs[i].label_key);
637 auto it = strings.find(regs[i].label_key);
638 if (it != strings.end()) {
639 auto pick = [&](
const std::string& l) ->
const char* {
640 auto lit = it->second.by_lang.find(l);
641 return lit != it->second.by_lang.end() ? lit->second.c_str() :
nullptr;
644 if (!label) label = pick(
"en");
645 if (!label && !it->second.by_lang.empty())
646 label = it->second.by_lang.begin()->second.c_str();
651 std::strncpy(out[i].label, label,
sizeof(out[i].label) - 1);
652 out[i].
label[
sizeof(out[i].
label) - 1] =
'\0';
667 if (active_ && active_->id() ==
id)
return true;
668 for (
const auto& p : background_)
if (p->id() ==
id)
return true;
674 for (
const auto& p : background_)
if (p->id() ==
id)
return true;
680 return active_ && active_->manifest().capabilities.background;
685 return active_ && active_->manifest().capabilities.prevent_sleep;
690 return !background_.empty();
695 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
697 if (!active_)
return;
699 (void)active_->callI(
"plugin_on_button",
700 {static_cast<int32_t>(button_code)}, &rc);
701 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_button");
706 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
708 if (!active_)
return;
710 (void)active_->callI(
"plugin_on_action",
711 {static_cast<int32_t>(action_id),
712 static_cast<int32_t>(idx),
713 static_cast<int32_t>(user_data)}, &rc);
714 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_action");
718 uint32_t idx, uint32_t user_data)
721 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
724 bool found = (active_.get() == plugin);
726 for (
auto& p : background_)
if (p.get() == plugin) { found =
true;
break; }
731 (void)plugin->
callI(
"plugin_on_action",
732 {static_cast<int32_t>(action_id),
733 static_cast<int32_t>(idx),
734 static_cast<int32_t>(user_data)}, &rc);
735 if (plugin->
lastCallTrapped()) handleTrap(*plugin,
"plugin_on_action");
740 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
743 const int32_t hi =
static_cast<int32_t
>(uptime_ms >> 32);
744 const int32_t lo =
static_cast<int32_t
>(uptime_ms & 0xFFFFFFFFu);
746 (void)active_->callI(
"plugin_on_tick", {lo, hi}, &rc);
747 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_tick");
749 Plugin* trapped[kMaxTrapsPerDispatch];
750 size_t n_trapped = 0;
751 for (
auto& p : background_) {
752 (void)p->callI(
"plugin_on_tick", {lo, hi}, &rc);
753 if (p->lastCallTrapped() && n_trapped < kMaxTrapsPerDispatch)
754 trapped[n_trapped++] = p.get();
756 for (
size_t i = 0; i < n_trapped; ++i) handleTrap(*trapped[i],
"plugin_on_tick");
763 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
767 (void)active_->callI(
"plugin_on_event",
768 {static_cast<int32_t>(event_type),
769 static_cast<int32_t>(value)}, &rc);
770 if (active_->lastCallTrapped()) handleTrap(*active_,
"plugin_on_event");
772 Plugin* trapped[kMaxTrapsPerDispatch];
773 size_t n_trapped = 0;
774 for (
auto& p : background_) {
775 (void)p->callI(
"plugin_on_event",
776 {static_cast<int32_t>(event_type),
777 static_cast<int32_t>(value)}, &rc);
778 if (p->lastCallTrapped() && n_trapped < kMaxTrapsPerDispatch)
779 trapped[n_trapped++] = p.get();
781 for (
size_t i = 0; i < n_trapped; ++i) handleTrap(*trapped[i],
"plugin_on_event");
786 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
787 if (!lock)
return false;
789 Plugin* target = (active_ && active_->id() ==
id) ? active_.get() :
nullptr;
791 for (
auto& p : background_)
if (p->id() ==
id) { target = p.get();
break; }
793 if (!target || !target->
hasExport(
"plugin_on_cmd"))
return false;
795 pending_cmd_.assign(cmd ? cmd :
"", len);
797 (void)target->
callI(
"plugin_on_cmd", {static_cast<int32_t>(len)}, &rc);
798 pending_cmd_.clear();
806 size_t n = pending_cmd_.size();
807 if (n >= out_size) n = out_size - 1;
808 std::memcpy(out, pending_cmd_.data(), n);
810 pending_cmd_.clear();
811 return static_cast<int>(n);
816 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
818 if (active_) {
if (!visitor(*active_))
return; }
819 for (
auto& p : background_) {
820 if (!visitor(*p))
return;
826 ScopedLock lock(
static_cast<SemaphoreHandle_t
>(call_mutex_));
828 if (active_) active_->loadLangOverlay();
829 for (
auto& p : background_) p->loadLangOverlay();
832void PluginManager::tickTaskTrampoline(
void* arg)
835 vTaskDelete(
nullptr);
838void PluginManager::tickTaskLoop()
840 TickType_t last = xTaskGetTickCount();
841 while (!tick_stop_) {
842 vTaskDelayUntil(&last, pdMS_TO_TICKS(TICK_INTERVAL_MS));
843 if (tick_stop_)
break;
844 maybeRefreshMessageIndex();
845 if (pending_stop_.exchange(
false, std::memory_order_acq_rel)) {
856void PluginManager::startTickTask()
858 if (tick_task_)
return;
860 TaskHandle_t handle =
nullptr;
861 if (xTaskCreate(&PluginManager::tickTaskTrampoline,
862 "plg_tick", TICK_STACK_BYTES,
this,
863 TICK_PRIORITY, &handle) != pdPASS) {
864 LOG_E(
TAG,
"failed to spawn plg_tick task");
870void PluginManager::stopTickTask()
872 if (!tick_task_)
return;
876 vTaskDelay(pdMS_TO_TICKS(TICK_INTERVAL_MS * 2));
877 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_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)
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.
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)
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 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::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 void unmount()
Unmount the plugins partition (rarely used; mostly tests).
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::vector< std::string > listPluginIds()
Discover all installed plugin ids. A plugin is recognised by the presence of both <id>....
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_gpio_on_unload(void *plugin)
void plg_http_on_unload(void *plugin)
void plg_msg_on_unload(void *plugin)
void plg_socket_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
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