Files
XCEngine/new_editor/app/Host/AutoScreenshot.cpp

196 lines
5.6 KiB
C++

#include "AutoScreenshot.h"
#include "NativeRenderer.h"
#include <chrono>
#include <cctype>
#include <cstdlib>
#include <cstdio>
#include <sstream>
#include <system_error>
namespace XCEngine::UI::Editor::Host {
namespace {
bool IsAutoCaptureOnStartupEnabled() {
const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP");
if (value == nullptr || value[0] == '\0') {
return false;
}
std::string normalized = value;
for (char& character : normalized) {
character = static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
}
return normalized != "0" &&
normalized != "false" &&
normalized != "off" &&
normalized != "no";
}
} // namespace
void AutoScreenshotController::Initialize(const std::filesystem::path& captureRoot) {
m_captureRoot = captureRoot.lexically_normal();
m_historyRoot = (m_captureRoot / "history").lexically_normal();
m_latestCapturePath = (m_captureRoot / "latest.png").lexically_normal();
m_captureCount = 0;
m_capturePending = false;
m_pendingReason.clear();
m_lastCaptureSummary.clear();
m_lastCaptureError.clear();
if (IsAutoCaptureOnStartupEnabled()) {
RequestCapture("startup");
}
}
void AutoScreenshotController::Shutdown() {
m_capturePending = false;
m_pendingReason.clear();
}
void AutoScreenshotController::RequestCapture(std::string reason) {
m_pendingReason = reason.empty() ? "capture" : std::move(reason);
m_capturePending = true;
}
void AutoScreenshotController::CaptureIfRequested(
NativeRenderer& renderer,
const ::XCEngine::UI::UIDrawData& drawData,
unsigned int width,
unsigned int height,
bool framePresented) {
if (!m_capturePending) {
return;
}
if (!framePresented || drawData.Empty() || width == 0u || height == 0u) {
return;
}
std::error_code errorCode = {};
std::filesystem::create_directories(m_captureRoot, errorCode);
if (errorCode) {
m_lastCaptureError = "Failed to create screenshot directory: " + m_captureRoot.string();
m_lastCaptureSummary = "AutoShot failed";
m_capturePending = false;
return;
}
std::filesystem::create_directories(m_historyRoot, errorCode);
if (errorCode) {
m_lastCaptureError = "Failed to create screenshot directory: " + m_historyRoot.string();
m_lastCaptureSummary = "AutoShot failed";
m_capturePending = false;
return;
}
std::string captureError = {};
const std::filesystem::path historyPath = BuildHistoryCapturePath(m_pendingReason);
if (!renderer.CaptureToPng(drawData, width, height, historyPath, captureError)) {
m_lastCaptureError = std::move(captureError);
m_lastCaptureSummary = "AutoShot failed";
m_capturePending = false;
return;
}
errorCode.clear();
std::filesystem::copy_file(
historyPath,
m_latestCapturePath,
std::filesystem::copy_options::overwrite_existing,
errorCode);
if (errorCode) {
m_lastCaptureError = "Failed to update latest screenshot: " + m_latestCapturePath.string();
m_lastCaptureSummary = "AutoShot failed";
m_capturePending = false;
return;
}
++m_captureCount;
m_lastCaptureError.clear();
m_lastCaptureSummary =
"Shot: latest.png | " + historyPath.filename().string();
m_capturePending = false;
m_pendingReason.clear();
}
bool AutoScreenshotController::HasPendingCapture() const {
return m_capturePending;
}
const std::filesystem::path& AutoScreenshotController::GetLatestCapturePath() const {
return m_latestCapturePath;
}
const std::string& AutoScreenshotController::GetLastCaptureSummary() const {
return m_lastCaptureSummary;
}
const std::string& AutoScreenshotController::GetLastCaptureError() const {
return m_lastCaptureError;
}
std::filesystem::path AutoScreenshotController::BuildHistoryCapturePath(std::string_view reason) const {
std::ostringstream filename;
filename << BuildTimestampString()
<< '_'
<< (m_captureCount + 1u)
<< '_'
<< SanitizeReason(reason)
<< ".png";
return (m_historyRoot / filename.str()).lexically_normal();
}
std::string AutoScreenshotController::BuildTimestampString() {
const auto now = std::chrono::system_clock::now();
const std::time_t currentTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime = {};
localtime_s(&localTime, &currentTime);
char buffer[32] = {};
std::snprintf(
buffer,
sizeof(buffer),
"%04d%02d%02d_%02d%02d%02d",
localTime.tm_year + 1900,
localTime.tm_mon + 1,
localTime.tm_mday,
localTime.tm_hour,
localTime.tm_min,
localTime.tm_sec);
return buffer;
}
std::string AutoScreenshotController::SanitizeReason(std::string_view reason) {
std::string sanitized = {};
sanitized.reserve(reason.size());
bool lastWasSeparator = false;
for (const unsigned char value : reason) {
if (std::isalnum(value)) {
sanitized.push_back(static_cast<char>(std::tolower(value)));
lastWasSeparator = false;
continue;
}
if (!lastWasSeparator) {
sanitized.push_back('_');
lastWasSeparator = true;
}
}
while (!sanitized.empty() && sanitized.front() == '_') {
sanitized.erase(sanitized.begin());
}
while (!sanitized.empty() && sanitized.back() == '_') {
sanitized.pop_back();
}
return sanitized.empty() ? "capture" : sanitized;
}
} // namespace XCEngine::UI::Editor::Host