9#include "absl/strings/str_format.h"
13#include "imgui/imgui.h"
15#if defined(YAZE_WITH_LIBPNG)
26#if defined(YAZE_WITH_LIBPNG)
28struct PngMemoryReader {
34void PngReadFromMemory(png_structp png_ptr, png_bytep out, png_size_t count) {
35 auto* reader =
static_cast<PngMemoryReader*
>(png_get_io_ptr(png_ptr));
36 if (reader->offset + count > reader->size) {
37 png_error(png_ptr,
"Read past end of PNG data");
40 std::memcpy(out, reader->data + reader->offset, count);
41 reader->offset += count;
52 const std::string& encoded) {
54 static constexpr int kTable[256] = {
55 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
56 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
57 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
58 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
59 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
60 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
61 -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
62 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
63 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
64 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
65 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
66 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
67 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
68 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
69 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
70 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
74 std::vector<uint8_t> out;
75 out.reserve((encoded.size() / 4) * 3);
80 for (
unsigned char c : encoded) {
84 accum = (accum << 6) | static_cast<uint32_t>(val);
88 out.push_back(
static_cast<uint8_t
>((accum >> bits) & 0xFF));
100 std::vector<uint8_t>& rgba_out,
101 int& width_out,
int& height_out) {
102#if !defined(YAZE_WITH_LIBPNG)
109 if (png_data.size() < 8)
114 reinterpret_cast<png_bytep
>(
const_cast<uint8_t*
>(png_data.data())), 0,
119 png_structp png_ptr =
120 png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
124 png_infop info_ptr = png_create_info_struct(png_ptr);
126 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
130 if (setjmp(png_jmpbuf(png_ptr))) {
131 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
135 PngMemoryReader reader{png_data.data(), 0, png_data.size()};
136 png_set_read_fn(png_ptr, &reader, PngReadFromMemory);
138 png_read_info(png_ptr, info_ptr);
140 png_uint_32 w = png_get_image_width(png_ptr, info_ptr);
141 png_uint_32 h = png_get_image_height(png_ptr, info_ptr);
142 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
143 png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
147 png_set_strip_16(png_ptr);
148 if (color_type == PNG_COLOR_TYPE_PALETTE)
149 png_set_palette_to_rgb(png_ptr);
150 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
151 png_set_expand_gray_1_2_4_to_8(png_ptr);
152 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
153 png_set_tRNS_to_alpha(png_ptr);
154 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY ||
155 color_type == PNG_COLOR_TYPE_PALETTE)
156 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
157 if (color_type == PNG_COLOR_TYPE_GRAY ||
158 color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
159 png_set_gray_to_rgb(png_ptr);
161 png_read_update_info(png_ptr, info_ptr);
163 width_out =
static_cast<int>(w);
164 height_out =
static_cast<int>(h);
165 rgba_out.resize(w * h * 4);
167 std::vector<png_bytep> row_pointers(h);
168 for (png_uint_32 y = 0; y < h; ++y) {
169 row_pointers[y] = rgba_out.data() + y * w * 4;
172 png_read_image(png_ptr, row_pointers.data());
173 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
202 std::shared_ptr<emu::mesen::MesenSocketClient> client) {
215 auto status =
client_->Connect();
228 auto status =
client_->Connect(socket_path);
272 SDL_Renderer* renderer =
nullptr;
275 SDL_Window* window = SDL_GetMouseFocus();
277 window = SDL_GetKeyboardFocus();
280 renderer = SDL_GetRenderer(window);
288 texture_ = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32,
289 SDL_TEXTUREACCESS_STREAMING, width, height);
291 SDL_SetTextureBlendMode(
texture_, SDL_BLENDMODE_BLEND);
298 int width,
int height) {
305 SDL_UpdateTexture(
texture_,
nullptr, rgba.data(), width * 4);
325 auto t0 = std::chrono::steady_clock::now();
327 auto result =
client_->Screenshot();
336 if (png_bytes.empty()) {
342 std::vector<uint8_t> rgba;
353 auto t1 = std::chrono::steady_clock::now();
355 std::chrono::duration<float, std::milli>(t1 - t0).count();
369 ImGui::PushID(
"MesenScreenshotPanel");
382 if (ImGui::BeginChild(
"MesenScreenshot_Panel", ImVec2(0, 0),
true)) {
383 if (ImGui::IsWindowAppearing()) {
412 ImGui::TextColored(theme.accent_color,
"%s Mesen2 Screenshot Preview",
416 ImGui::SameLine(ImGui::GetWindowWidth() - 100);
418 float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 2.0f);
419 ImVec4 color = ImVec4(0.1f, pulse, 0.3f, 1.0f);
422 ImGui::TextColored(theme.status_error,
"%s Disconnected",
ICON_MD_ERROR);
428 ImGui::TextDisabled(
"Socket");
429 const char* preview =
431 selected_socket_index_ < static_cast<int>(
socket_paths_.size()))
433 :
"No sockets found";
434 ImGui::SetNextItemWidth(-40);
435 if (ImGui::BeginCombo(
"##ss_socket_combo", preview)) {
436 for (
int i = 0; i < static_cast<int>(
socket_paths_.size()); ++i) {
444 ImGui::SetItemDefaultFocus();
454 ImGui::TextDisabled(
"Path");
455 ImGui::SetNextItemWidth(-1);
456 ImGui::InputTextWithHint(
"##ss_socket_path",
"/tmp/mesen2-12345.sock",
506 ImGui::SetNextItemWidth(120);
507 ImGui::SliderFloat(
"##ss_fps", &
target_fps_, 1.0f, 30.0f,
"%.0f FPS");
522 ImVec2 avail = ImGui::GetContentRegionAvail();
526 float placeholder_h = std::max(avail.y - 40.0f, 100.0f);
527 ImVec2 center(avail.x * 0.5f, placeholder_h * 0.5f);
528 ImGui::BeginChild(
"##ss_placeholder", ImVec2(0, placeholder_h),
true);
529 ImGui::SetCursorPos(ImVec2(center.x - 80, center.y - 10));
530 ImGui::TextDisabled(
"No screenshot captured");
538 float max_h = std::max(avail.y - 60.0f, 100.0f);
539 float display_w = avail.x;
540 float display_h = display_w / src_aspect;
542 if (display_h > max_h) {
544 display_w = display_h * src_aspect;
548 float offset_x = (avail.x - display_w) * 0.5f;
550 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset_x);
553 ImGui::Image(
reinterpret_cast<ImTextureID
>(
texture_),
554 ImVec2(display_w, display_h), ImVec2(0, 0), ImVec2(1, 1));
558 ImVec2 img_min = ImGui::GetItemRectMin();
559 ImGui::GetWindowDrawList()->AddRectFilled(
560 img_min, ImVec2(img_min.x + 50, img_min.y + 20),
561 IM_COL32(200, 60, 60, 200));
562 ImGui::GetWindowDrawList()->AddText(ImVec2(img_min.x + 4, img_min.y + 2),
563 IM_COL32(255, 255, 255, 255),
"Stale");
569 theme.text_secondary_color,
"Frame #%llu | %dx%d | Latency: %.1f ms",
582 ImGui::TextColored(theme.text_secondary_color,
"Connected (auto-detect)");
584 ImGui::TextColored(theme.text_secondary_color,
"Connected to %s",
590 float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 3.0f);
591 ImGui::TextColored(ImVec4(0.2f, pulse, 0.2f, 1.0f),
596 ImGui::TextDisabled(
"Paused");
float last_capture_latency_ms_
std::string connection_error_
static std::vector< uint8_t > DecodeBase64(const std::string &encoded)
void SetClient(std::shared_ptr< emu::mesen::MesenSocketClient > client)
std::vector< std::string > socket_paths_
std::shared_ptr< emu::mesen::MesenSocketClient > client_
int selected_socket_index_
void EnsureTexture(int width, int height)
static bool DecodePngToRgba(const std::vector< uint8_t > &png_data, std::vector< uint8_t > &rgba_out, int &width_out, int &height_out)
void ConnectToPath(const std::string &socket_path)
std::string status_message_
void DrawConnectionHeader()
char socket_path_buffer_[256]
void UpdateTexture(const std::vector< uint8_t > &rgba, int width, int height)
void DrawControlsToolbar()
static void SetClient(std::shared_ptr< MesenSocketClient > client)
static std::shared_ptr< MesenSocketClient > GetOrCreate()
static std::vector< std::string > ListAvailableSockets()
List available Mesen2 sockets on the system.
#define ICON_MD_CAMERA_ALT
#define ICON_MD_PLAY_ARROW
#define ICON_MD_AUTO_MODE
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_FIBER_MANUAL_RECORD
#define ICON_MD_PHOTO_CAMERA
const AgentUITheme & GetTheme()