CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
PluginListView.cpp
Go to the documentation of this file.
6#include "cdc_ui/I18n.h"
7#include "cdc_log.h"
8
9#include <cstdio>
10
11namespace cdc::plugin_manager {
12
13static const char* TAG = "PLG_UI";
14static PluginListView* s_active = nullptr;
15
16// Plugin id targeted by the currently-open context menu (key 3). The menu's
17// callbacks take no arguments, so the selection is stashed here.
18static std::string s_ctxPluginId;
20
24static void onCtxStop()
25{
27 if (!s_ctxPluginId.empty()) {
29 }
30 if (s_active) {
31 s_active->refresh();
32 }
33}
34
38static void onCtxDisable()
39{
41 if (!s_ctxPluginId.empty()) {
42 if (PluginManager::instance().setPluginDisabled(s_ctxPluginId, true)) {
43 cdc::ui::showToastInfo(cdc::ui::tr("core.plugin_disabled"), 1800);
44 } else {
45 cdc::ui::showToastError("Disable failed", 3000);
46 }
47 }
48 if (s_active) {
49 s_active->refresh();
50 }
51}
52
56static void onCtxEnable()
57{
59 if (!s_ctxPluginId.empty()) {
60 if (PluginManager::instance().setPluginDisabled(s_ctxPluginId, false)) {
61 cdc::ui::showToastInfo(cdc::ui::tr("core.plugin_enabled"), 1800);
62 } else {
63 cdc::ui::showToastError("Enable failed", 3000);
64 }
65 }
66 if (s_active) {
67 s_active->refresh();
68 }
69}
70
72static void reportStartResult(StartResult result)
73{
74 if (result == StartResult::Ok) return;
75 static char msg[64];
76 const char* label = "Start failed";
77 switch (result) {
78 case StartResult::PluginAlreadyRunning: label = "Already running"; break;
79 case StartResult::ManifestInvalid: label = "Manifest invalid"; break;
80 case StartResult::CapabilityRejected: label = "Capabilities rejected"; break;
81 case StartResult::WamrLoadFailed: label = "WASM load failed"; break;
82 case StartResult::PluginInitFailed: label = "plugin_init failed"; break;
83 case StartResult::PrerequisiteFailed: label = "Prerequisite failed"; break;
84 case StartResult::PluginOnEnterFailed: label = "plugin_on_enter missing"; break;
85 case StartResult::Busy: label = "Plugin busy"; break;
86 case StartResult::PluginDisabled: label = cdc::ui::tr("core.plugin_disabled"); break;
87 default: break;
88 }
89 std::snprintf(msg, sizeof(msg), "%s\nErr %d", label, static_cast<int>(result));
90 cdc::ui::showToastError(msg, 3000);
91}
92
96static void onCtxStart()
97{
99 if (!s_ctxPluginId.empty()) {
101 }
102 if (s_active) {
103 s_active->refresh();
104 }
105}
106
108
110
111void PluginListView::onEnter(void* /*context*/)
112{
113 s_active = this;
114 list_.setOnSelect(&PluginListView::onSelectStatic);
115 list_.setOnMenu (&PluginListView::onMenuStatic);
116 rebuildItems();
117}
118
120{
121 if (s_active == this) s_active = nullptr;
122}
123
125{
126 s_active = this;
127 // A plugin declaring capabilities.background keeps ticking after the user
128 // leaves its view; tell them so before we demote it off the foreground.
129 if (PluginManager::instance().activePluginIsBackground()) {
130 cdc::ui::showToastInfo(cdc::ui::tr("core.plugin_bg_running"), 1800);
131 }
133 rebuildItems();
134 list_.markDirty();
135}
136
137void PluginListView::render(bool partial)
138{
139 list_.render(partial);
140}
141
143{
144 return list_.onKey(key);
145}
146
148{
149 return cdc::ui::tr("core.hint_plugin_list");
150}
151
153{
154 rebuildItems();
155 list_.markDirty();
156}
157
158void PluginListView::rebuildItems()
159{
160 auto& mgr = PluginManager::instance();
161 ids_ = mgr.listInstalledIds();
162 labels_.clear();
163 items_.clear();
164 labels_.reserve(ids_.size());
165 items_.reserve(ids_.size());
166
167 for (const auto& id : ids_) {
168 std::string display = id;
169 if (auto mf = PluginManager::instance().getManifest(id)) {
170 auto it = mf->i18n_meta.find("name");
171 if (it != mf->i18n_meta.end() && !it->second.by_lang.empty()) {
172 auto def = it->second.by_lang.find(mf->default_language);
173 if (def != it->second.by_lang.end()) {
174 display = def->second;
175 } else {
176 display = it->second.by_lang.begin()->second;
177 }
178 }
179 }
180 labels_.push_back(std::move(display));
181 // Mark as running for an already-background plugin AND for the active
182 // plugin that is about to be demoted to background (the demotion is
183 // async, so this would otherwise miss the indicator on first open).
184 const bool running = mgr.isRunningInBackground(id) ||
185 (mgr.activePluginIsBackground() && mgr.activePluginId() == id);
186 const bool disabled = mgr.isPluginDisabled(id);
187 // List icons are drawn as raw CP437 glyphs; 'X' marks a disabled plugin.
188 const uint8_t icon = disabled ? static_cast<uint8_t>('X')
189 : (running ? UI_ICON_SUN : 0);
190 items_.push_back(cdc::ui::ListItem{labels_.back().c_str(), icon, false, nullptr});
191 }
192
193 list_.init("Plugins", items_.data(), static_cast<uint16_t>(items_.size()));
194 list_.setEmptyText("No plugins installed");
195 list_.setHint(cdc::ui::tr("core.hint_plugin_list"));
196}
197
198void PluginListView::onSelectStatic(uint16_t index, void*)
199{
200 if (s_active) s_active->onSelect(index);
201}
202
203void PluginListView::onMenuStatic(uint16_t index, void*)
204{
205 if (s_active) s_active->onMenu(index);
206}
207
208void PluginListView::onSelect(uint16_t index)
209{
210 if (index >= ids_.size()) return;
211 const auto& id = ids_[index];
212 LOG_I(TAG, "start plugin %s", id.c_str());
213
215}
216
217void PluginListView::onMenu(uint16_t index)
218{
219 if (index >= ids_.size()) return;
220 s_ctxPluginId = ids_[index];
221
222 auto& mgr = PluginManager::instance();
223 const bool running = mgr.isRunningInBackground(s_ctxPluginId) ||
224 (mgr.activePluginIsBackground() && mgr.activePluginId() == s_ctxPluginId);
225 const bool disabled = mgr.isPluginDisabled(s_ctxPluginId);
226
227 uint8_t count = 0;
228 if (disabled) {
229 s_ctxItems[count++] = cdc::ui::ContextMenuItem{cdc::ui::tr("core.enable"), &onCtxEnable};
230 } else {
231 s_ctxItems[count++] = running
232 ? cdc::ui::ContextMenuItem{cdc::ui::tr("core.stop"), &onCtxStop}
233 : cdc::ui::ContextMenuItem{cdc::ui::tr("core.start"), &onCtxStart};
234 s_ctxItems[count++] = cdc::ui::ContextMenuItem{cdc::ui::tr("core.disable"), &onCtxDisable};
235 }
236 cdc::ui::showContextMenu(cdc::ui::tr("core.actions"), s_ctxItems, count);
237}
238
239} // namespace cdc::plugin_manager
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
Main-menu entry "Plugins" - lists all installed WASM plugins.
Discovers, loads, runs and unloads WASM plugins on the badge.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
static PluginListView * active() noexcept
Currently-mounted PluginListView instance, or nullptr if none.
const char * getFooterHint() const override
void onEnter(void *context=nullptr) override
void render(bool partial) override
cdc::ui::InputResult onKey(char key) override
static PluginManager & instance() noexcept
bool unloadFromRam(const std::string &id)
#define UI_ICON_SUN
Definition host_api.h:750
CDC Badge OS plugin host API - canonical C ABI contract.
static void onCtxDisable()
Context-menu Disable callback: persistently disables and unloads.
static void onCtxEnable()
Context-menu Enable callback: removes the persistent disabled marker.
static void onCtxStart()
Context-menu Start callback: starts the selected (stopped) plugin.
static PluginListView * s_active
static cdc::ui::ContextMenuItem s_ctxItems[2]
static const char * TAG
static std::string s_ctxPluginId
static void reportStartResult(StartResult result)
Show a toast describing a non-Ok plugin start result.
static void onCtxStop()
Context-menu Stop callback: force-unloads the selected plugin.
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
InputResult
Definition IView.h:10
void hideContextMenu()
Hides the active context menu modal.
ContextMenuView * showContextMenu(const char *title, const ContextMenuItem *items, uint8_t count)
Shows the shared context menu instance as modal.
void showToastInfo(const char *message, uint16_t durationMs=1500)
Shows an informational toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.