feat: restore MCP settings and board customizations

This commit is contained in:
Adolfo Reyna
2026-09-10 21:15:10 -04:00
parent c8ee763f3d
commit 61fc67cf4c
26 changed files with 5551 additions and 9 deletions
@@ -3,10 +3,12 @@
#include <Tactility/service/displayidle/DisplayIdleService.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/mcp/McpSystem.h>
#include "BouncingBallsScreensaver.h"
#include "MatrixRainScreensaver.h"
#include "MystifyScreensaver.h"
#include "McpScreensaver.h"
#include "Screensaver.h"
#include "StackChanScreensaver.h"
@@ -127,6 +129,9 @@ void DisplayIdleService::activateScreensaver() {
case settings::display::ScreensaverType::StackChan:
screensaver = std::make_unique<StackChanScreensaver>();
break;
case settings::display::ScreensaverType::McpScreen:
screensaver = std::make_unique<McpScreensaver>();
break;
case settings::display::ScreensaverType::None:
default:
// Just black screen, no animated screensaver
@@ -273,6 +278,49 @@ bool DisplayIdleService::isScreensaverActive() const {
return screensaverOverlay != nullptr;
}
void DisplayIdleService::startMcpScreensaver() {
if (!lvgl_try_lock(200)) {
LOG_W(TAG, "startMcpScreensaver: failed to acquire LVGL lock");
return;
}
if (screensaverOverlay != nullptr) {
const auto& mcpState = mcp::getState();
if (mcpState.drawArea != nullptr) {
lvgl_unlock();
return;
}
if (screensaver) {
screensaver->stop();
screensaver.reset();
}
lv_obj_delete(screensaverOverlay);
screensaverOverlay = nullptr;
}
screensaverActiveCounter = 0;
backlightOff = false;
setBacklightBrightness(cachedDisplaySettings.backlightDuty == 0
? 255 : cachedDisplaySettings.backlightDuty);
lv_coord_t screenW = lv_display_get_horizontal_resolution(nullptr);
lv_coord_t screenH = lv_display_get_vertical_resolution(nullptr);
screensaverOverlay = lv_obj_create(lv_layer_top());
lv_obj_remove_style_all(screensaverOverlay);
lv_obj_set_size(screensaverOverlay, LV_PCT(100), LV_PCT(100));
lv_obj_set_pos(screensaverOverlay, 0, 0);
lv_obj_set_style_bg_color(screensaverOverlay, lv_color_black(), 0);
lv_obj_set_style_bg_opa(screensaverOverlay, LV_OPA_COVER, 0);
lv_obj_add_flag(screensaverOverlay, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(screensaverOverlay, stopScreensaverCb, LV_EVENT_CLICKED, this);
screensaver = std::make_unique<McpScreensaver>();
screensaver->start(screensaverOverlay, screenW, screenH);
lvgl_unlock();
displayDimmed = true;
LOG_I(TAG, "MCP screensaver activated");
}
void DisplayIdleService::reloadSettings() {
// Set flag for thread-safe reload - actual reload happens in tick()
settingsReloadRequested.store(true, std::memory_order_release);
@@ -0,0 +1,100 @@
#ifdef ESP_PLATFORM
#include "McpScreensaver.h"
#include <Tactility/mcp/McpSystem.h>
#include <tactility/log.h>
constexpr auto* TAG = "McpScreensaver";
#include <esp_heap_caps.h>
namespace tt::service::displayidle {
void McpScreensaver::start(lv_obj_t* overlay, lv_coord_t screenW, lv_coord_t screenH) {
auto& state = mcp::getState();
// Full-screen canvas on the overlay
lv_obj_t* canvas = lv_canvas_create(overlay);
lv_obj_set_size(canvas, screenW, screenH);
lv_obj_set_pos(canvas, 0, 0);
lv_obj_set_style_radius(canvas, 0, LV_PART_MAIN);
lv_obj_set_style_border_width(canvas, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(canvas, 0, LV_PART_MAIN);
lv_obj_remove_flag(canvas, LV_OBJ_FLAG_SCROLLABLE);
// Allocate framebuffer (prefer SPIRAM)
size_t requiredSize = (size_t)screenW * screenH * sizeof(uint16_t);
framebuffer = (uint16_t*)heap_caps_malloc(requiredSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (framebuffer == nullptr) {
framebuffer = (uint16_t*)heap_caps_malloc(requiredSize, MALLOC_CAP_8BIT);
}
framebufferSize = (framebuffer != nullptr) ? requiredSize : 0;
if (framebuffer == nullptr) {
LOG_E(TAG, "Failed to allocate %uB framebuffer", (unsigned)requiredSize);
lv_obj_t* err = lv_label_create(canvas);
lv_label_set_text(err, "Framebuffer alloc failed");
lv_obj_center(err);
return;
}
// Fill with a dark slate background (inverted for display path)
size_t pixelCount = (size_t)screenW * screenH;
for (size_t i = 0; i < pixelCount; ++i) {
framebuffer[i] = ~0x18E3; // dark blue-grey
}
lv_canvas_set_buffer(canvas, framebuffer, screenW, screenH, LV_COLOR_FORMAT_RGB565);
// Waiting label (removed on first MCP draw via lv_obj_clean)
lv_obj_t* waitLabel = lv_label_create(canvas);
lv_label_set_text(waitLabel, "Waiting for LLM...");
lv_obj_set_style_text_color(waitLabel, lv_color_black(), LV_PART_MAIN); // white on screen (inverted)
lv_obj_align(waitLabel, LV_ALIGN_CENTER, 0, -20);
lv_obj_t* resLabel = lv_label_create(canvas);
lv_label_set_text_fmt(resLabel, "Display: %dx%d", (int)screenW, (int)screenH);
lv_color_t resColor = lv_palette_lighten(LV_PALETTE_BLUE, 3);
lv_obj_set_style_text_color(resLabel, lv_color_make(~resColor.red, ~resColor.green, ~resColor.blue), LV_PART_MAIN);
lv_obj_align(resLabel, LV_ALIGN_CENTER, 0, 10);
// Register with McpSystemState
std::lock_guard<std::mutex> lock(state.mutex);
state.drawArea = canvas;
state.framebuffer = framebuffer;
state.framebufferSize = framebufferSize;
state.displayWidth = (uint16_t)screenW;
state.displayHeight = (uint16_t)screenH;
state.drawWidth = (uint16_t)screenW;
state.drawHeight = (uint16_t)screenH;
// Don't reset overrideActive — if the LLM already drew, we keep the content
LOG_I(TAG, "McpScreensaver started (%dx%d)", (int)screenW, (int)screenH);
}
void McpScreensaver::stop() {
auto& state = mcp::getState();
{
std::lock_guard<std::mutex> lock(state.mutex);
state.drawArea = nullptr;
state.framebuffer = nullptr;
state.framebufferSize = 0;
state.overrideActive = false;
}
if (framebuffer != nullptr) {
heap_caps_free(framebuffer);
framebuffer = nullptr;
framebufferSize = 0;
}
LOG_I(TAG, "McpScreensaver stopped");
}
void McpScreensaver::update(lv_coord_t /*screenW*/, lv_coord_t /*screenH*/) {
// MCP draws on demand via HTTP — no per-frame animation needed
}
} // namespace tt::service::displayidle
#endif // ESP_PLATFORM
@@ -0,0 +1,30 @@
#pragma once
#ifdef ESP_PLATFORM
#include "Screensaver.h"
#include <cstdint>
namespace tt::service::displayidle {
/**
* MCP Screen screensaver.
* Creates a full-screen LVGL canvas on the overlay and registers it in
* McpSystemState so that MCP HTTP draw commands can paint to it.
* Dismissed by a touch event (handled by the parent DisplayIdle overlay).
*/
class McpScreensaver final : public Screensaver {
uint16_t* framebuffer = nullptr;
size_t framebufferSize = 0;
public:
McpScreensaver() = default;
~McpScreensaver() override = default;
void start(lv_obj_t* overlay, lv_coord_t screenW, lv_coord_t screenH) override;
void stop() override;
void update(lv_coord_t screenW, lv_coord_t screenH) override;
};
} // namespace tt::service::displayidle
#endif // ESP_PLATFORM