guard JSON serialization for widget state capture for cross platform
This commit is contained in:
@@ -246,14 +246,15 @@ target_sources(yaze PRIVATE
|
|||||||
${CMAKE_SOURCE_DIR}/src/app/core/widget_state_capture.cc
|
${CMAKE_SOURCE_DIR}/src/app/core/widget_state_capture.cc
|
||||||
${CMAKE_SOURCE_DIR}/src/app/core/widget_state_capture.h)
|
${CMAKE_SOURCE_DIR}/src/app/core/widget_state_capture.h)
|
||||||
|
|
||||||
target_include_directories(yaze PRIVATE
|
|
||||||
${CMAKE_SOURCE_DIR}/third_party/json/include)
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Optional gRPC Support for ImGuiTestHarness
|
# Optional gRPC Support for ImGuiTestHarness
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
if(YAZE_WITH_GRPC)
|
if(YAZE_WITH_GRPC)
|
||||||
message(STATUS "Adding gRPC ImGuiTestHarness to yaze target")
|
message(STATUS "Adding gRPC ImGuiTestHarness to yaze target")
|
||||||
|
|
||||||
|
target_include_directories(yaze PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/third_party/json/include)
|
||||||
|
target_compile_definitions(yaze PRIVATE YAZE_WITH_JSON)
|
||||||
|
|
||||||
# Generate C++ code from .proto using the helper function from cmake/grpc.cmake
|
# Generate C++ code from .proto using the helper function from cmake/grpc.cmake
|
||||||
target_add_protobuf(yaze
|
target_add_protobuf(yaze
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ set(
|
|||||||
app/core/project.cc
|
app/core/project.cc
|
||||||
app/core/window.cc
|
app/core/window.cc
|
||||||
app/core/asar_wrapper.cc
|
app/core/asar_wrapper.cc
|
||||||
|
app/core/widget_state_capture.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
if (WIN32 OR MINGW OR (UNIX AND NOT APPLE))
|
if (WIN32 OR MINGW OR (UNIX AND NOT APPLE))
|
||||||
|
|||||||
@@ -7,11 +7,85 @@
|
|||||||
#else
|
#else
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if defined(YAZE_WITH_JSON)
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
#if !defined(YAZE_WITH_JSON)
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string EscapeJsonString(const std::string& value) {
|
||||||
|
std::string escaped;
|
||||||
|
escaped.reserve(value.size() + 2);
|
||||||
|
escaped.push_back('"');
|
||||||
|
|
||||||
|
for (unsigned char c : value) {
|
||||||
|
switch (c) {
|
||||||
|
case '"':
|
||||||
|
escaped.append("\\\"");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
escaped.append("\\\\");
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
escaped.append("\\b");
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
escaped.append("\\f");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
escaped.append("\\n");
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
escaped.append("\\r");
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
escaped.append("\\t");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (c <= 0x1F) {
|
||||||
|
escaped.append(absl::StrFormat("\\\\u%04X", static_cast<int>(c)));
|
||||||
|
} else {
|
||||||
|
escaped.push_back(static_cast<char>(c));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
escaped.push_back('"');
|
||||||
|
return escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* BoolToJson(bool value) { return value ? "true" : "false"; }
|
||||||
|
|
||||||
|
std::string FormatFloat(float value) {
|
||||||
|
// Match typical JSON formatting without trailing zeros when possible.
|
||||||
|
return absl::StrFormat("%.4f", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FormatFloatCompact(float value) {
|
||||||
|
std::string formatted = FormatFloat(value);
|
||||||
|
|
||||||
|
// Trim trailing zeros while keeping at least one decimal place.
|
||||||
|
if (formatted.find('.') != std::string::npos) {
|
||||||
|
while (!formatted.empty() && formatted.back() == '0') {
|
||||||
|
formatted.pop_back();
|
||||||
|
}
|
||||||
|
if (!formatted.empty() && formatted.back() == '.') {
|
||||||
|
formatted.push_back('0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
#endif // !defined(YAZE_WITH_JSON)
|
||||||
|
|
||||||
std::string CaptureWidgetState() {
|
std::string CaptureWidgetState() {
|
||||||
WidgetState state;
|
WidgetState state;
|
||||||
|
|
||||||
@@ -81,48 +155,123 @@ std::string CaptureWidgetState() {
|
|||||||
// When UI test engine / ImGui internals aren't available, provide a minimal
|
// When UI test engine / ImGui internals aren't available, provide a minimal
|
||||||
// payload so downstream systems still receive structured JSON. This keeps
|
// payload so downstream systems still receive structured JSON. This keeps
|
||||||
// builds that exclude the UI test engine (e.g., Windows release) working.
|
// builds that exclude the UI test engine (e.g., Windows release) working.
|
||||||
return R"({"warning": "Widget state capture unavailable (UI test engine disabled)"})";
|
return "{\"warning\": \"Widget state capture unavailable (UI test engine disabled)\"}";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return SerializeWidgetStateToJson(state);
|
return SerializeWidgetStateToJson(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SerializeWidgetStateToJson(const WidgetState& state) {
|
std::string SerializeWidgetStateToJson(const WidgetState& state) {
|
||||||
|
#if defined(YAZE_WITH_JSON)
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
|
|
||||||
// Basic state
|
|
||||||
j["frame_count"] = state.frame_count;
|
j["frame_count"] = state.frame_count;
|
||||||
j["frame_rate"] = state.frame_rate;
|
j["frame_rate"] = state.frame_rate;
|
||||||
|
|
||||||
// Window state
|
|
||||||
j["focused_window"] = state.focused_window;
|
j["focused_window"] = state.focused_window;
|
||||||
j["focused_widget"] = state.focused_widget;
|
j["focused_widget"] = state.focused_widget;
|
||||||
j["hovered_widget"] = state.hovered_widget;
|
j["hovered_widget"] = state.hovered_widget;
|
||||||
j["visible_windows"] = state.visible_windows;
|
j["visible_windows"] = state.visible_windows;
|
||||||
j["open_popups"] = state.open_popups;
|
j["open_popups"] = state.open_popups;
|
||||||
|
|
||||||
// Navigation state
|
|
||||||
j["navigation"] = {
|
j["navigation"] = {
|
||||||
{"nav_id", absl::StrFormat("0x%08X", state.nav_id)},
|
{"nav_id", absl::StrFormat("0x%08X", state.nav_id)},
|
||||||
{"nav_active", state.nav_active}
|
{"nav_active", state.nav_active}};
|
||||||
};
|
|
||||||
|
|
||||||
// Input state
|
|
||||||
nlohmann::json mouse_buttons;
|
nlohmann::json mouse_buttons;
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; ++i) {
|
||||||
mouse_buttons.push_back(state.mouse_down[i]);
|
mouse_buttons.push_back(state.mouse_down[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
j["input"] = {
|
j["input"] = {
|
||||||
{"mouse_buttons", mouse_buttons},
|
{"mouse_buttons", mouse_buttons},
|
||||||
{"mouse_pos", {state.mouse_pos_x, state.mouse_pos_y}},
|
{"mouse_pos", {state.mouse_pos_x, state.mouse_pos_y}},
|
||||||
{"modifiers", {
|
{"modifiers",
|
||||||
{"ctrl", state.ctrl_pressed},
|
{{"ctrl", state.ctrl_pressed},
|
||||||
{"shift", state.shift_pressed},
|
{"shift", state.shift_pressed},
|
||||||
{"alt", state.alt_pressed}
|
{"alt", state.alt_pressed}}}};
|
||||||
}}
|
|
||||||
};
|
return j.dump(2);
|
||||||
|
#else
|
||||||
return j.dump(2); // Pretty print with 2-space indent
|
std::string json;
|
||||||
|
json.reserve(512);
|
||||||
|
|
||||||
|
json.append("{\n");
|
||||||
|
json.append(" \"frame_count\": ");
|
||||||
|
json.append(std::to_string(state.frame_count));
|
||||||
|
json.append(",\n");
|
||||||
|
|
||||||
|
json.append(" \"frame_rate\": ");
|
||||||
|
json.append(FormatFloatCompact(state.frame_rate));
|
||||||
|
json.append(",\n");
|
||||||
|
|
||||||
|
json.append(" \"focused_window\": ");
|
||||||
|
json.append(EscapeJsonString(state.focused_window));
|
||||||
|
json.append(",\n");
|
||||||
|
|
||||||
|
json.append(" \"focused_widget\": ");
|
||||||
|
json.append(EscapeJsonString(state.focused_widget));
|
||||||
|
json.append(",\n");
|
||||||
|
|
||||||
|
json.append(" \"hovered_widget\": ");
|
||||||
|
json.append(EscapeJsonString(state.hovered_widget));
|
||||||
|
json.append(",\n");
|
||||||
|
|
||||||
|
json.append(" \"visible_windows\": [");
|
||||||
|
for (size_t i = 0; i < state.visible_windows.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
json.append(", ");
|
||||||
|
}
|
||||||
|
json.append(EscapeJsonString(state.visible_windows[i]));
|
||||||
|
}
|
||||||
|
json.append("],\n");
|
||||||
|
|
||||||
|
json.append(" \"open_popups\": [");
|
||||||
|
for (size_t i = 0; i < state.open_popups.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
json.append(", ");
|
||||||
|
}
|
||||||
|
json.append(EscapeJsonString(state.open_popups[i]));
|
||||||
|
}
|
||||||
|
json.append("],\n");
|
||||||
|
|
||||||
|
json.append(" \"navigation\": {\n");
|
||||||
|
json.append(" \"nav_id\": ");
|
||||||
|
json.append(EscapeJsonString(absl::StrFormat("0x%08X", state.nav_id)));
|
||||||
|
json.append(",\n");
|
||||||
|
json.append(" \"nav_active\": ");
|
||||||
|
json.append(BoolToJson(state.nav_active));
|
||||||
|
json.append("\n },\n");
|
||||||
|
|
||||||
|
json.append(" \"input\": {\n");
|
||||||
|
json.append(" \"mouse_buttons\": [");
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
json.append(", ");
|
||||||
|
}
|
||||||
|
json.append(BoolToJson(state.mouse_down[i]));
|
||||||
|
}
|
||||||
|
json.append("],\n");
|
||||||
|
|
||||||
|
json.append(" \"mouse_pos\": [");
|
||||||
|
json.append(FormatFloatCompact(state.mouse_pos_x));
|
||||||
|
json.append(", ");
|
||||||
|
json.append(FormatFloatCompact(state.mouse_pos_y));
|
||||||
|
json.append("],\n");
|
||||||
|
|
||||||
|
json.append(" \"modifiers\": {\n");
|
||||||
|
json.append(" \"ctrl\": ");
|
||||||
|
json.append(BoolToJson(state.ctrl_pressed));
|
||||||
|
json.append(",\n");
|
||||||
|
json.append(" \"shift\": ");
|
||||||
|
json.append(BoolToJson(state.shift_pressed));
|
||||||
|
json.append(",\n");
|
||||||
|
json.append(" \"alt\": ");
|
||||||
|
json.append(BoolToJson(state.alt_pressed));
|
||||||
|
json.append("\n }\n");
|
||||||
|
json.append(" }\n");
|
||||||
|
json.append("}\n");
|
||||||
|
|
||||||
|
return json;
|
||||||
|
#endif // defined(YAZE_WITH_JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
|
|||||||
Reference in New Issue
Block a user