#ifdef ESP_PLATFORM #include "McpScreensaver.h" #include #include #include namespace tt::service::displayidle { static const auto LOGGER = Logger("McpScreensaver"); 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) { LOGGER.error("Failed to allocate {}B 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 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 LOGGER.info("McpScreensaver started ({}x{})", (int)screenW, (int)screenH); } void McpScreensaver::stop() { auto& state = mcp::getState(); { std::lock_guard 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; } LOGGER.info("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