feat: restore MCP settings and board customizations
This commit is contained in:
@@ -107,6 +107,9 @@ void showNoInternet(Context* ctx) {
|
||||
}
|
||||
|
||||
void showApps(Context* ctx) {
|
||||
if (ctx->contentWrapper == nullptr) {
|
||||
return;
|
||||
}
|
||||
// Refresh rebuilds the list from scratch (cached copy, then again once the network fetch
|
||||
// lands), which would otherwise reset the user's scroll position each time.
|
||||
int32_t scrollY;
|
||||
@@ -266,7 +269,9 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
void destroyWidgets(void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
ctx->scrollY = lv_obj_get_scroll_y(ctx->contentWrapper);
|
||||
if (ctx->contentWrapper != nullptr) {
|
||||
ctx->scrollY = lv_obj_get_scroll_y(ctx->contentWrapper);
|
||||
}
|
||||
ctx->contentWrapper = nullptr;
|
||||
ctx->refreshButton = nullptr;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ extern const ::AppManifest manifest = {
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||
.stack = { .depth = 2400, .desired_memory_capability = 0 },
|
||||
.stack = { .depth = 3072, .desired_memory_capability = 0 },
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <Tactility/service/displayidle/DisplayIdleService.h>
|
||||
#endif
|
||||
#include <Tactility/lvgl/Lvgl.h>
|
||||
#include <Tactility/settings/DisplaySettings.h>
|
||||
|
||||
#include <app/event.h>
|
||||
@@ -90,6 +91,21 @@ void onOrientationSet(lv_event_t* event) {
|
||||
}
|
||||
}
|
||||
|
||||
void onFontSizeChanged(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
uint32_t selected_index = lv_dropdown_get_selected(dropdown);
|
||||
if (selected_index >= static_cast<uint32_t>(settings::display::FontSize::Count)) {
|
||||
return;
|
||||
}
|
||||
auto selected_size = static_cast<settings::display::FontSize>(selected_index);
|
||||
if (selected_size != ctx->displaySettings.fontSize) {
|
||||
ctx->displaySettings.fontSize = selected_size;
|
||||
ctx->displaySettingsUpdated = true;
|
||||
lvgl::applyFontSize(selected_size);
|
||||
}
|
||||
}
|
||||
|
||||
void onTimeoutSwitch(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
auto* sw = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
@@ -208,6 +224,23 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
// Set the dropdown to match current orientation enum
|
||||
lv_dropdown_set_selected(orientation_dropdown, static_cast<uint16_t>(ctx->displaySettings.orientation));
|
||||
|
||||
// Font size
|
||||
|
||||
auto* font_size_wrapper = lv_obj_create(main_wrapper);
|
||||
lv_obj_set_size(font_size_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_all(font_size_wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_width(font_size_wrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* font_size_label = lv_label_create(font_size_wrapper);
|
||||
lv_label_set_text(font_size_label, "Font size");
|
||||
lv_obj_align(font_size_label, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
auto* font_size_dropdown = lv_dropdown_create(font_size_wrapper);
|
||||
lv_dropdown_set_options(font_size_dropdown, "Small\nDefault\nLarge");
|
||||
lv_obj_align(font_size_dropdown, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
lv_obj_add_event_cb(font_size_dropdown, onFontSizeChanged, LV_EVENT_VALUE_CHANGED, ctx);
|
||||
lv_dropdown_set_selected(font_size_dropdown, static_cast<uint16_t>(ctx->displaySettings.fontSize));
|
||||
|
||||
// Screen timeout
|
||||
// Note: DisplayIdleService doesn't act on these settings for kernel-driver displays yet
|
||||
// (it only looks up the deprecated tt::hal::display::DisplayDevice), so these currently
|
||||
@@ -274,7 +307,7 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
|
||||
ctx->screensaverDropdown = lv_dropdown_create(screensaver_wrapper);
|
||||
// Note: order correlates with settings::display::ScreensaverType enum order
|
||||
lv_dropdown_set_options(ctx->screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan");
|
||||
lv_dropdown_set_options(ctx->screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan\nMCP Screen");
|
||||
lv_obj_align(ctx->screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
lv_obj_add_event_cb(ctx->screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, ctx);
|
||||
lv_dropdown_set_selected(ctx->screensaverDropdown, static_cast<uint16_t>(ctx->displaySettings.screensaverType));
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/lvgl/Lvgl.h>
|
||||
#include <Tactility/settings/McpSettings.h>
|
||||
#include <Tactility/settings/WebServerSettings.h>
|
||||
#include <Tactility/service/webserver/WebServerService.h>
|
||||
#include <app/event.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <lvgl_window_manager/window_manager.h>
|
||||
#include <lvgl/widgets/toolbar.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/log.h>
|
||||
#include <esp_netif.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::app::mcpsettings {
|
||||
constexpr auto* TAG = "McpSettingsApp";
|
||||
extern const ::AppManifest manifest;
|
||||
namespace {
|
||||
struct Context {
|
||||
uint32_t appInstanceId;
|
||||
settings::mcp::McpSettings mcpSettings;
|
||||
settings::webserver::WebServerSettings wsSettings;
|
||||
lv_obj_t* switchMcpEnabled = nullptr;
|
||||
lv_obj_t* labelUrlValue = nullptr;
|
||||
};
|
||||
|
||||
void updateUrlDisplay(Context* ctx) {
|
||||
if (ctx->labelUrlValue == nullptr) return;
|
||||
if (!ctx->mcpSettings.mcpEnabled) { lv_label_set_text(ctx->labelUrlValue, "Disabled"); return; }
|
||||
std::string url = "http://";
|
||||
bool ipAdded = false;
|
||||
for (const char* key : {"WIFI_STA_DEF", "WIFI_AP_DEF"}) {
|
||||
auto* netif = esp_netif_get_handle_from_ifkey(key);
|
||||
if (netif != nullptr) {
|
||||
esp_netif_ip_info_t info;
|
||||
if (esp_netif_get_ip_info(netif, &info) == ESP_OK && info.ip.addr != 0) {
|
||||
char ip[16]; snprintf(ip, sizeof(ip), IPSTR, IP2STR(&info.ip));
|
||||
url += ip; ipAdded = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ipAdded) url += ctx->wsSettings.wifiMode == settings::webserver::WiFiMode::AccessPoint ? "192.168.4.1" : "Connecting...";
|
||||
if (url.starts_with("http://") && ctx->wsSettings.webServerPort != 80) url += ":" + std::to_string(ctx->wsSettings.webServerPort);
|
||||
url += "/api/mcp";
|
||||
lv_label_set_text(ctx->labelUrlValue, url.c_str());
|
||||
}
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
app_event_emit_close(ctx->appInstanceId);
|
||||
}
|
||||
|
||||
void onMcpEnabledSwitch(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
const bool enabled = lv_obj_has_state(ctx->switchMcpEnabled, LV_STATE_CHECKED);
|
||||
getMainDispatcher().dispatch([ctx, enabled] {
|
||||
ctx->mcpSettings.mcpEnabled = enabled;
|
||||
lvgl_lock(); updateUrlDisplay(ctx); lvgl_unlock();
|
||||
if (!settings::mcp::save(ctx->mcpSettings)) LOG_W(TAG, "Failed to persist MCP settings");
|
||||
service::webserver::getPubsub()->publish(service::webserver::WebServerEvent::WebServerSettingsChanged);
|
||||
service::webserver::setWebServerEnabled(enabled);
|
||||
});
|
||||
}
|
||||
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
ctx->wsSettings = settings::webserver::loadOrGetDefault();
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
auto* toolbar = lvgl_toolbar_create(parent, "MCP Settings");
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||
ctx->switchMcpEnabled = lvgl_toolbar_add_switch_action(toolbar);
|
||||
if (ctx->mcpSettings.mcpEnabled) lv_obj_add_state(ctx->switchMcpEnabled, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(ctx->switchMcpEnabled, onMcpEnabledSwitch, LV_EVENT_VALUE_CHANGED, ctx);
|
||||
auto* main = lv_obj_create(parent); lv_obj_set_flex_flow(main, LV_FLEX_FLOW_COLUMN); lv_obj_set_width(main, LV_PCT(100)); lv_obj_set_flex_grow(main, 1);
|
||||
auto* wrapper = lv_obj_create(main); lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_pad_all(wrapper, 10, LV_STATE_DEFAULT); lv_obj_set_style_border_width(wrapper, 1, LV_STATE_DEFAULT); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_flex_cross_place(wrapper, LV_FLEX_ALIGN_START, 0);
|
||||
auto* title = lv_label_create(wrapper); lv_label_set_text(title, "MCP Endpoint URL:");
|
||||
ctx->labelUrlValue = lv_label_create(wrapper); updateUrlDisplay(ctx);
|
||||
auto* info = lv_label_create(main); lv_label_set_long_mode(info, LV_LABEL_LONG_WRAP); lv_obj_set_width(info, LV_PCT(95));
|
||||
lv_label_set_text(info, "MCP (Model Context Protocol) Screen service allows LLMs to interact with the device screen, audio, and tools directly.\n\nEndpoints:\n- POST /api/mcp (JSON-RPC tools)\n- POST /api/screen/raw (big-endian RGB565 writes)\n\nTo show the LLM canvas, select 'MCP Screen' in Settings -> Display -> Screensaver. The canvas also pops up automatically when an LLM sends a draw command.");
|
||||
}
|
||||
|
||||
int32_t appMain(int, char**) {
|
||||
Context ctx{}; ctx.appInstanceId = app_scheduler_current_app_id(); ctx.mcpSettings = settings::mcp::loadOrGetDefault();
|
||||
TaskEventGroup group{}; task_event_group_construct(&group); AppEventSubscription sub{}; check(app_event_subscribe(&sub, &group) == ERROR_NONE);
|
||||
auto window = window_manager_create(ctx.appInstanceId, createWidgets, &ctx); bool close = false;
|
||||
while (!close) { task_event_group_wait_any(&group, nullptr, portMAX_DELAY); AppEvent event{}; while (app_event_poll(&sub, &event) == ERROR_NONE) if (event.type == APP_EVENT_CLOSE) { close = true; break; } }
|
||||
window_manager_remove(window); check(app_event_unsubscribe(&sub) == ERROR_NONE); task_event_group_destruct(&group); return 0;
|
||||
}
|
||||
}
|
||||
extern const ::AppManifest manifest = { .id = "McpSettings", .name = "MCP Screen", .category = APP_CATEGORY_SETTINGS, .location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }, .flags = 0, .stack = { .depth = 8192, .desired_memory_capability = 0 } };
|
||||
}
|
||||
#endif
|
||||
@@ -118,7 +118,7 @@ extern const ::AppManifest manifest = {
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||
.stack = { .depth = 2400, .desired_memory_capability = 0 },
|
||||
.stack = { .depth = 3072, .desired_memory_capability = 0 },
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user