e3eb3fd415
- MCP 3.3K LOC: McpSystem 1899 + McpHandler 1029 + McpScreensaver + 2 apps
- Uses native tactility/log.h TAG macros, no old Logger.h
- DisplaySettings McpScreen enum, WebServer coexistence (MCP enabled starts HTTP)
- DisplayIdle: startMcpScreensaver + isDeviceCharging + lock inversion fix 3629ffef
- LVGL 512K PSRAM cache retained from previous perf patch
- Audio kept upstream es8311-module per note we might not need custom
142 lines
5.2 KiB
C++
142 lines
5.2 KiB
C++
#ifdef ESP_PLATFORM
|
|
|
|
#include <Tactility/Tactility.h>
|
|
#include <Tactility/mcp/McpSystem.h>
|
|
#include <Tactility/lvgl/Toolbar.h>
|
|
#include <Tactility/lvgl/LvglSync.h>
|
|
#include <tactility/log.h>
|
|
|
|
constexpr auto* TAG = "McpOverrideApp";
|
|
|
|
#include <lvgl.h>
|
|
#include <tactility/lvgl_icon_shared.h>
|
|
#include <esp_heap_caps.h>
|
|
|
|
namespace tt::app::mcpoverride {
|
|
|
|
|
|
class McpOverrideApp final : public App {
|
|
|
|
public:
|
|
void onCreate(AppContext& app) override {
|
|
// Prepare global state
|
|
auto& state = mcp::getState();
|
|
state.overrideActive = false;
|
|
}
|
|
|
|
void onShow(AppContext& app, lv_obj_t* parent) override {
|
|
LOG_I(TAG, "onShow: Starting MCP Override display canvas");
|
|
auto& state = mcp::getState();
|
|
|
|
lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
|
|
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
|
lv_obj_set_style_pad_all(parent, 0, LV_PART_MAIN);
|
|
lv_obj_set_style_pad_row(parent, 0, LV_PART_MAIN);
|
|
|
|
// Standard toolbar so the user can navigate back
|
|
lv_obj_t* toolbar = lvgl::toolbar_create(parent, app);
|
|
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
|
|
|
lv_obj_t* title_label = lv_label_create(toolbar);
|
|
lv_label_set_text(title_label, "MCP Override Screen");
|
|
|
|
// Create drawing canvas
|
|
state.drawArea = lv_canvas_create(parent);
|
|
lv_obj_set_width(state.drawArea, LV_PCT(100));
|
|
lv_obj_set_flex_grow(state.drawArea, 1);
|
|
lv_obj_set_style_radius(state.drawArea, 0, LV_PART_MAIN);
|
|
lv_obj_set_style_border_width(state.drawArea, 0, LV_PART_MAIN);
|
|
lv_obj_set_style_pad_all(state.drawArea, 0, LV_PART_MAIN);
|
|
lv_obj_remove_flag(state.drawArea, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
// Get display metrics
|
|
lv_display_t* display = lv_obj_get_display(parent);
|
|
state.displayWidth = lv_display_get_horizontal_resolution(display);
|
|
state.displayHeight = lv_display_get_vertical_resolution(display);
|
|
|
|
lv_obj_update_layout(parent);
|
|
state.drawWidth = lv_obj_get_content_width(state.drawArea);
|
|
state.drawHeight = lv_obj_get_content_height(state.drawArea);
|
|
|
|
// Allocate framebuffer
|
|
size_t required_size = (size_t)state.drawWidth * state.drawHeight * sizeof(uint16_t);
|
|
if (state.framebuffer == nullptr || state.framebufferSize != required_size) {
|
|
if (state.framebuffer != nullptr) {
|
|
heap_caps_free(state.framebuffer);
|
|
state.framebuffer = nullptr;
|
|
}
|
|
state.framebuffer = (uint16_t*)heap_caps_malloc(required_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (state.framebuffer == nullptr) {
|
|
state.framebuffer = (uint16_t*)heap_caps_malloc(required_size, MALLOC_CAP_8BIT);
|
|
}
|
|
state.framebufferSize = state.framebuffer == nullptr ? 0 : required_size;
|
|
}
|
|
|
|
if (state.framebuffer == nullptr) {
|
|
LOG_E(TAG, "Failed to allocate %u bytes framebuffer", (unsigned)required_size);
|
|
lv_obj_t* error = lv_label_create(state.drawArea);
|
|
lv_label_set_text(error, "Framebuffer allocation failed");
|
|
lv_obj_center(error);
|
|
return;
|
|
}
|
|
|
|
lv_canvas_set_buffer(
|
|
state.drawArea,
|
|
state.framebuffer,
|
|
state.drawWidth,
|
|
state.drawHeight,
|
|
LV_COLOR_FORMAT_RGB565
|
|
);
|
|
|
|
// Initialize welcome/waiting screen if LLM hasn't written anything yet
|
|
if (!state.overrideActive) {
|
|
// Fill with a nice dark blue/slate color
|
|
for (size_t i = 0; i < (size_t)state.drawWidth * state.drawHeight; ++i) {
|
|
state.framebuffer[i] = 0x18E3;
|
|
}
|
|
state.drawColor = 1;
|
|
|
|
lv_obj_t* welcome_label = lv_label_create(state.drawArea);
|
|
lv_label_set_text(welcome_label, "Waiting for LLM...");
|
|
lv_obj_set_style_text_color(welcome_label, lv_color_white(), LV_PART_MAIN);
|
|
lv_obj_align(welcome_label, LV_ALIGN_CENTER, 0, -20);
|
|
|
|
lv_obj_t* desc_label = lv_label_create(state.drawArea);
|
|
lv_label_set_text_fmt(desc_label, "Display Resolution: %ux%u", state.drawWidth, state.drawHeight);
|
|
lv_obj_set_style_text_color(desc_label, lv_palette_lighten(LV_PALETTE_BLUE, 3), LV_PART_MAIN);
|
|
lv_obj_align(desc_label, LV_ALIGN_CENTER, 0, 10);
|
|
}
|
|
}
|
|
|
|
void onHide(AppContext& app) override {
|
|
LOG_I(TAG, "onHide: Tearing down MCP Override canvas");
|
|
auto& state = mcp::getState();
|
|
state.drawArea = nullptr;
|
|
if (state.framebuffer != nullptr) {
|
|
heap_caps_free(state.framebuffer);
|
|
state.framebuffer = nullptr;
|
|
state.framebufferSize = 0;
|
|
}
|
|
state.overrideActive = false;
|
|
|
|
// Stop any running tone or recording to prevent stuck state
|
|
state.audioRunning = false;
|
|
}
|
|
|
|
void onDestroy(AppContext& app) override {
|
|
onHide(app);
|
|
}
|
|
};
|
|
|
|
extern const AppManifest manifest = {
|
|
.appId = "one.tactility.mcpscreen", // Keep the original appId for compatibility
|
|
.appName = "MCP Override Screen",
|
|
.appIcon = LVGL_ICON_SHARED_TOOLBAR,
|
|
.appCategory = Category::System,
|
|
.createApp = create<McpOverrideApp>
|
|
};
|
|
|
|
} // namespace
|
|
|
|
#endif // ESP_PLATFORM
|