/** * @file Session.cpp * @brief 离屏会话实现:隐藏窗口、加载数据、逐帧渲染与抓帧。 * * ⚠️ 全程不显示窗口、不移动真实鼠标、不抢焦点:所有"鼠标"都是直接调用 * `Application::HandleMouseMove()` 注入的合成消息。 */ #include "OffscreenSupport.hpp" namespace jdi { namespace tools { namespace { /** @brief 窄字符串(本地代码页)→ 宽字符串。 */ std::wstring WidenLocal(const std::string& text) { if (text.empty()) { return std::wstring(); } // 先按 UTF-8 试一次(Agent / 终端最常见),失败再退回本地代码页。 int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, nullptr, 0); UINT codePage = CP_UTF8; if (length <= 1) { codePage = CP_ACP; length = MultiByteToWideChar(CP_ACP, 0, text.c_str(), -1, nullptr, 0); } if (length <= 1) { return std::wstring(); } std::wstring wide(static_cast(length - 1), L'\0'); MultiByteToWideChar(codePage, 0, text.c_str(), -1, &wide[0], length); return wide; } } // namespace bool Session::Start() { if (m_started) { return true; } m_app = &Application::Instance(); if (!m_app->Initialize(GetModuleHandleW(nullptr), SW_HIDE)) { m_app = nullptr; return false; } m_started = true; return true; } void Session::Stop() { if (m_app != nullptr && m_started) { m_app->Shutdown(); m_started = false; } } bool Session::Load(const std::string& path) { if (m_app == nullptr) { return false; } const std::wstring wide = WidenLocal(path); if (wide.empty()) { return false; } if (!m_app->LoadFile(wide)) { return false; } m_loadedPath = path; m_app->SynchronizeView(true); m_app->Relayout(); return true; } bool Session::RenderFrame() { if (m_app == nullptr) { return false; } // 强制整帧:`m_chromeDirty` 为真时标签栏/工具栏/面板/绘图区都会重绘, // 否则 `Render()` 可能走"只重绘绘图区"的快路径,抓到的不是完整画面。 m_app->m_chromeDirty = true; LARGE_INTEGER frequency = {}; LARGE_INTEGER start = {}; LARGE_INTEGER stop = {}; QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&start); m_app->Render(); QueryPerformanceCounter(&stop); m_lastRenderMs = static_cast(stop.QuadPart - start.QuadPart) * 1000.0 / static_cast(frequency.QuadPart); return CaptureFrame(*m_app, m_lastFrame); } void Session::ShowSignals(const std::vector& refs) { if (m_app == nullptr) { return; } TabConfig& tab = m_app->ActiveTab(); tab.SetAllVisible(false); for (const SignalRef& ref : refs) { tab.SetVisible(ref, true); } m_app->SynchronizeView(true); m_app->Relayout(); } void Session::ShowFirstSignals(const int limit) { if (m_app == nullptr) { return; } const DataSet& dataSet = m_app->m_dataSet; std::vector refs; for (int groupIndex = 0; groupIndex < static_cast(dataSet.GroupCount()); ++groupIndex) { const SignalGroup* group = dataSet.GroupAt(groupIndex); if (group == nullptr) { continue; } for (int signalIndex = 0; signalIndex < static_cast(group->signals.size()); ++signalIndex) { if (limit > 0 && static_cast(refs.size()) >= limit) { break; } refs.push_back(SignalRef{groupIndex, signalIndex}); } if (limit > 0 && static_cast(refs.size()) >= limit) { break; } } ShowSignals(refs); } void Session::HideAll() { if (m_app == nullptr) { return; } m_app->ActiveTab().SetAllVisible(false); m_app->SynchronizeView(true); m_app->Relayout(); } bool Session::MouseMove(const int x, const int y) { if (m_app == nullptr) { return false; } return m_app->HandleMouseMove(x, y, false); } gd::RectF Session::PlotArea() const { if (m_app == nullptr) { return gd::RectF(0.0f, 0.0f, 0.0f, 0.0f); } return m_app->m_plot.Area(); } gd::RectF Session::LaneRect(const int index) const { if (m_app == nullptr || index < 0) { return gd::RectF(0.0f, 0.0f, 0.0f, 0.0f); } const std::vector& lanes = m_app->m_plot.m_lanes; if (index >= static_cast(lanes.size())) { return gd::RectF(0.0f, 0.0f, 0.0f, 0.0f); } return lanes[static_cast(index)].rect; } int Session::LaneCount() const { if (m_app == nullptr) { return 0; } return static_cast(m_app->m_plot.m_lanes.size()); } void Session::ClientSize(int& width, int& height) const { width = 0; height = 0; if (m_app == nullptr || m_app->m_hwnd == nullptr) { return; } RECT rect = {}; GetClientRect(m_app->m_hwnd, &rect); width = rect.right - rect.left; height = rect.bottom - rect.top; if (width <= 0 || height <= 0) { // 隐藏窗口在某些 DPI 路径下可能还没有客户区尺寸,退回后备缓冲区尺寸。 const Frame& frame = m_lastFrame; width = frame.width; height = frame.height; } } } // namespace tools } // namespace jdi