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.
7#include "cdc_ui/I18n.h"
8#include "cdc_log.h"
9
10#include <cstdio>
11
12namespace cdc::plugin_manager {
13
14static const char* TAG = "PLG_UI";
15static PluginListView* s_active = nullptr;
16
17// Plugin id targeted by the currently-open context menu (key 3). The menu's
18// callbacks take no arguments, so the selection is stashed here.
19static std::string s_ctxPluginId;
21
25static void onCtxStop()
26{
28 if (!s_ctxPluginId.empty()) {
30 }
31 if (s_active) {
32 s_active->refresh();
33 }
34}
35
39static void onCtxDisable()
40{
42 if (!s_ctxPluginId.empty()) {
43 if (PluginManager::instance().setPluginDisabled(s_ctxPluginId, true)) {
44 cdc::ui::showToastInfo(cdc::ui::tr("core.plugin_disabled"), 1800);
45 } else {
46 cdc::ui::showToastError("Disable failed", 3000);
47 }
48 }
49 if (s_active) {
50 s_active->refresh();
51 }
52}
53
57static void onCtxEnable()
58{
60 if (!s_ctxPluginId.empty()) {
61 if (PluginManager::instance().setPluginDisabled(s_ctxPluginId, false)) {
62 cdc::ui::showToastInfo(cdc::ui::tr("core.plugin_enabled"), 1800);
63 } else {
64 cdc::ui::showToastError("Enable failed", 3000);
65 }
66 }
67 if (s_active) {
68 s_active->refresh();
69 }
70}
71
73static void reportStartResult(StartResult result)
74{
75 if (result == StartResult::Ok) return;
76 static char msg[64];
77 const char* label = "Start failed";
78 switch (result) {
79 case StartResult::PluginAlreadyRunning: label = "Already running"; break;
80 case StartResult::ManifestInvalid: label = "Manifest invalid"; break;
81 case StartResult::CapabilityRejected: label = "Capabilities rejected"; break;
82 case StartResult::WamrLoadFailed: label = "WASM load failed"; break;
83 case StartResult::PluginInitFailed: label = "plugin_init failed"; break;
84 case StartResult::PrerequisiteFailed: label = "Prerequisite failed"; break;
85 case StartResult::PluginOnEnterFailed: label = "plugin_on_enter missing"; break;
86 case StartResult::Busy: label = "Plugin busy"; break;
87 case StartResult::PluginDisabled: label = cdc::ui::tr("core.plugin_disabled"); break;
88 default: break;
89 }
90 std::snprintf(msg, sizeof(msg), "%s\nErr %d", label, static_cast<int>(result));
91 cdc::ui::showToastError(msg, 3000);
92}
93
97static void onCtxStart()
98{
100 if (!s_ctxPluginId.empty()) {
102 }
103 if (s_active) {
104 s_active->refresh();
105 }
106}
107
113static void onUninstallConfirmed(void*)
114{
115 if (!s_ctxPluginId.empty()) {
117 cdc::ui::showToastInfo(cdc::ui::tr("core.deleted"), 1800);
118 }
119 if (s_active) {
120 s_active->refresh();
121 }
122}
123
127static void onCtxUninstall()
128{
130 if (s_ctxPluginId.empty()) return;
131 static std::string body;
132 body = std::string(cdc::ui::tr("core.uninstall")) + " " + s_ctxPluginId + "?\n"
133 + cdc::ui::tr("core.sure");
134 cdc::ui::showConfirm(body.c_str(), &onUninstallConfirmed, nullptr,
136}
137
139
141
142void PluginListView::onEnter(void* /*context*/)
143{
144 s_active = this;
145 list_.setOnSelect(&PluginListView::onSelectStatic);
146 list_.setOnMenu (&PluginListView::onMenuStatic);
147 rebuildItems();
148}
149
151{
152 if (s_active == this) s_active = nullptr;
153}
154
156{
157 s_active = this;
158 // A plugin declaring capabilities.background keeps ticking after the user
159 // leaves its view; tell them so before we demote it off the foreground.
160 if (PluginManager::instance().activePluginIsBackground()) {
161 cdc::ui::showToastInfo(cdc::ui::tr("core.plugin_bg_running"), 1800);
162 }
164 rebuildItems();
165 list_.markDirty();
166}
167
168void PluginListView::render(bool partial)
169{
170 list_.render(partial);
171}
172
174{
175 return list_.onKey(key);
176}
177
179{
180 return cdc::ui::tr("core.hint_plugin_list");
181}
182
184{
185 rebuildItems();
186 list_.markDirty();
187}
188
189void PluginListView::rebuildItems()
190{
191 auto& mgr = PluginManager::instance();
192 ids_ = mgr.listInstalledIds();
193 labels_.clear();
194 items_.clear();
195 labels_.reserve(ids_.size());
196 items_.reserve(ids_.size());
197
198 for (const auto& id : ids_) {
199 std::string display = id;
200 if (auto mf = PluginManager::instance().getManifest(id)) {
201 auto it = mf->i18n_meta.find("name");
202 if (it != mf->i18n_meta.end() && !it->second.by_lang.empty()) {
203 auto def = it->second.by_lang.find(mf->default_language);
204 if (def != it->second.by_lang.end()) {
205 display = def->second;
206 } else {
207 display = it->second.by_lang.begin()->second;
208 }
209 }
210 }
211 labels_.push_back(std::move(display));
212 // Mark as running for an already-background plugin AND for the active
213 // plugin that is about to be demoted to background (the demotion is
214 // async, so this would otherwise miss the indicator on first open).
215 const bool running = mgr.isRunningInBackground(id) ||
216 (mgr.activePluginIsBackground() && mgr.activePluginId() == id);
217 const bool disabled = mgr.isPluginDisabled(id);
218 // List icons are drawn as raw CP437 glyphs; 'X' marks a disabled plugin.
219 const uint8_t icon = disabled ? static_cast<uint8_t>('X')
220 : (running ? UI_ICON_SUN : 0);
221 items_.push_back(cdc::ui::ListItem{labels_.back().c_str(), icon, false, nullptr});
222 }
223
224 list_.init("Plugins", items_.data(), static_cast<uint16_t>(items_.size()));
225 list_.setEmptyText("No plugins installed");
226 list_.setHint(cdc::ui::tr("core.hint_plugin_list"));
227}
228
229void PluginListView::onSelectStatic(uint16_t index, void*)
230{
231 if (s_active) s_active->onSelect(index);
232}
233
234void PluginListView::onMenuStatic(uint16_t index, void*)
235{
236 if (s_active) s_active->onMenu(index);
237}
238
239void PluginListView::onSelect(uint16_t index)
240{
241 if (index >= ids_.size()) return;
242 const auto& id = ids_[index];
243 LOG_I(TAG, "start plugin %s", id.c_str());
244
246}
247
248void PluginListView::onMenu(uint16_t index)
249{
250 if (index >= ids_.size()) return;
251 s_ctxPluginId = ids_[index];
252
253 auto& mgr = PluginManager::instance();
254 const bool running = mgr.isRunningInBackground(s_ctxPluginId) ||
255 (mgr.activePluginIsBackground() && mgr.activePluginId() == s_ctxPluginId);
256 const bool disabled = mgr.isPluginDisabled(s_ctxPluginId);
257
258 // A plugin resident in the background (not the one about to be demoted off
259 // the foreground) gets a clearer "Stop background" label; both stop via
260 // unloadFromRam.
261 const bool inBackground = mgr.isRunningInBackground(s_ctxPluginId);
262
263 uint8_t count = 0;
264 if (disabled) {
265 s_ctxItems[count++] = cdc::ui::ContextMenuItem{cdc::ui::tr("core.enable"), &onCtxEnable};
266 } else {
267 if (inBackground) {
268 s_ctxItems[count++] =
269 cdc::ui::ContextMenuItem{cdc::ui::tr("core.plugin_stop_bg"), &onCtxStop};
270 } else if (running) {
271 s_ctxItems[count++] = cdc::ui::ContextMenuItem{cdc::ui::tr("core.stop"), &onCtxStop};
272 } else {
273 s_ctxItems[count++] = cdc::ui::ContextMenuItem{cdc::ui::tr("core.start"), &onCtxStart};
274 }
275 s_ctxItems[count++] = cdc::ui::ContextMenuItem{cdc::ui::tr("core.disable"), &onCtxDisable};
276 }
277 s_ctxItems[count++] = cdc::ui::ContextMenuItem{cdc::ui::tr("core.uninstall"), &onCtxUninstall};
278 cdc::ui::showContextMenu(cdc::ui::tr("core.actions"), s_ctxItems, count);
279}
280
281} // 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)
bool uninstallPlugin(const std::string &id)
#define UI_ICON_SUN
Definition host_api.h:847
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[3]
static void onUninstallConfirmed(void *)
Confirm callback for uninstall: deletes the plugin and refreshes.
static const char * TAG
static std::string s_ctxPluginId
static void onCtxUninstall()
Context-menu Uninstall callback: asks for confirmation, then deletes.
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:209
InputResult
Definition IView.h:11
void showConfirm(const char *message, ConfirmView::ConfirmCallback onConfirm, ConfirmView::CancelCallback onCancel=nullptr, ConfirmView::Icon icon=ConfirmView::Icon::QUESTION, void *userData=nullptr)
Shows a shared modal confirmation dialog instance.
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.