Initial commit: MouseHighlighter project
A lightweight Windows mouse highlight overlay tool with halo circle and click ripple animations, built with C++17 and Win32 API.
This commit is contained in:
109
include/Config.h
Normal file
109
include/Config.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
/**
|
||||
* @brief 应用全局配置
|
||||
*
|
||||
* 从 INI 文件加载,可通过托盘菜单修改
|
||||
*/
|
||||
struct Config {
|
||||
// ========== 光晕圆形参数 ==========
|
||||
struct Halo {
|
||||
uint32_t colorARGB = 0x660099FF; // Alpha(102/255≈0.4) + 蓝紫色 ARGB
|
||||
float radius = 30.0f; // 半径 (像素)
|
||||
float thickness = 1.5f; // 线条厚度
|
||||
uint16_t drawSteps = 128; // 多边形近似圆的段数
|
||||
bool filled = false; // 是否填充 (true=实心圆, false=空心圆)
|
||||
uint8_t qualityLevel = 2; // 光晕画质: 1=普通, 2=高清, 3=超清
|
||||
} halo;
|
||||
|
||||
// ========== 波纹动画参数 ==========
|
||||
struct Ripple {
|
||||
bool enabled = true; // 是否启用波纹效果
|
||||
uint32_t colorARGB = 0x3300FF99; // Alpha(51/255≈0.2) + 青绿色
|
||||
float maxRadius = 120.0f; // 最大扩散半径
|
||||
uint32_t durationMS = 240; // 动画持续时间 (毫秒)
|
||||
float thickness = 2.5f; // 波纹线条厚度
|
||||
uint8_t maxConcurrent = 12; // 最多并发波纹数
|
||||
float alphaExponent = 3.0f; // 衰减阶次: alpha = (1-t)^n
|
||||
float radiusExponent = 1.0f; // 扩展阶次: 线性 (1.0)
|
||||
} ripple;
|
||||
|
||||
// ========== 坐标平滑参数 ==========
|
||||
struct Smoothing {
|
||||
float alpha = 0.0f; // EMA 系数 (0.0=不平滑, 推荐 0.25)
|
||||
uint32_t minUpdateDistPx = 1; // 最小更新距离 (像素)
|
||||
} smoothing;
|
||||
|
||||
// ========== 渲染节拍参数 ==========
|
||||
struct Timing {
|
||||
uint32_t targetFPS = 60; // 目标帧率
|
||||
uint32_t updateIntervalMS = 17; // 更新间隔 (1000/60 ≈ 16.67ms, 上舍入)
|
||||
uint32_t maxFrameTimeMS = 20; // 硬超时告警阈值
|
||||
} timing;
|
||||
|
||||
// ========== 性能监控参数 ==========
|
||||
struct Monitoring {
|
||||
uint32_t checkIntervalMS = 5000; // 监控检查间隔
|
||||
uint32_t memoryThresholdMB = 100; // 内存告警阈值
|
||||
uint32_t gdiHandleThreshold = 3000; // GDI 对象告警
|
||||
bool logToFile = false; // 是否输出调试日志
|
||||
} monitoring;
|
||||
|
||||
// ========== 系统集成参数 ==========
|
||||
struct System {
|
||||
bool enableTrayIcon = true; // 启用托盘图标
|
||||
bool autoStartup = false; // 开机自启 (Windows 注册表)
|
||||
uint32_t memoryCheckIntervalMS = 5000; // 内存检查间隔
|
||||
} system;
|
||||
|
||||
// ========== 方法 ==========
|
||||
|
||||
/**
|
||||
* @brief 从 INI 文件加载配置
|
||||
* @param path 文件路径 (宽字符)
|
||||
* @return true 成功,false 失败或文件不存在
|
||||
*/
|
||||
bool LoadFromINI(const wchar_t* path) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 保存配置到 INI 文件
|
||||
* @param path 文件路径 (宽字符)
|
||||
* @return true 成功,false 失败
|
||||
*/
|
||||
bool SaveToINI(const wchar_t* path) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief 验证所有参数的有效范围,必要时钳制
|
||||
*/
|
||||
void Validate() noexcept {
|
||||
// 光晕
|
||||
halo.radius = std::min(100.0f, std::max(10.0f, halo.radius));
|
||||
halo.thickness = std::min(5.0f, std::max(0.5f, halo.thickness));
|
||||
halo.drawSteps = std::min<uint16_t>(256, std::max<uint16_t>(32, halo.drawSteps));
|
||||
halo.qualityLevel = static_cast<uint8_t>(std::min(3u, std::max(1u, static_cast<unsigned>(halo.qualityLevel))));
|
||||
|
||||
// 波纹
|
||||
ripple.maxRadius = std::min(320.0f, std::max(50.0f, ripple.maxRadius));
|
||||
ripple.durationMS = std::min(3000u, std::max(100u, ripple.durationMS));
|
||||
ripple.thickness = std::min(5.0f, std::max(0.5f, ripple.thickness));
|
||||
ripple.maxConcurrent = std::min(32u, std::max(4u, (uint32_t)ripple.maxConcurrent));
|
||||
|
||||
// 平滑
|
||||
smoothing.alpha = std::min(0.5f, std::max(0.0f, smoothing.alpha));
|
||||
|
||||
// 定时
|
||||
timing.targetFPS = std::min(120u, std::max(30u, timing.targetFPS));
|
||||
timing.updateIntervalMS = std::max(8u, (uint32_t)(1000 / timing.targetFPS));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取默认配置
|
||||
inline Config GetDefaultConfig() {
|
||||
Config cfg;
|
||||
cfg.Validate();
|
||||
return cfg;
|
||||
}
|
||||
288
include/DataStructures.h
Normal file
288
include/DataStructures.h
Normal file
@@ -0,0 +1,288 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <wingdi.h>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
/**
|
||||
* @brief DIB (Device Independent Bitmap) 双缓冲管理
|
||||
*
|
||||
* 维护 ARGB32 位图,用于 UpdateLayeredWindow 输出
|
||||
*/
|
||||
class DIBBuffer {
|
||||
public:
|
||||
DIBBuffer() = default;
|
||||
|
||||
// 内存 DC & 位图
|
||||
HDC hdcMem = nullptr;
|
||||
HBITMAP hbmDIB = nullptr;
|
||||
uint32_t* pBits = nullptr;
|
||||
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
|
||||
/**
|
||||
* @brief 创建或重建 DIB 缓冲
|
||||
* @param w 宽度 (像素)
|
||||
* @param h 高度 (像素)
|
||||
* @param hdcScreen 参考设备上下文,通常来自 GetDC(nullptr)
|
||||
* @return true 成功,false 失败
|
||||
*/
|
||||
bool Create(uint32_t w, uint32_t h, HDC hdcScreen) noexcept {
|
||||
Release();
|
||||
|
||||
width = w;
|
||||
height = h;
|
||||
|
||||
// 创建兼容 DC
|
||||
hdcMem = CreateCompatibleDC(hdcScreen);
|
||||
if (!hdcMem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建 ARGB32 DIB
|
||||
BITMAPINFO bmi{};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = static_cast<LONG>(w);
|
||||
bmi.bmiHeader.biHeight = -static_cast<LONG>(h); // 负数: 顶部对齐
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32; // ARGB
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
hbmDIB = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS,
|
||||
reinterpret_cast<void**>(&pBits), nullptr, 0);
|
||||
if (!hbmDIB || !pBits) {
|
||||
DeleteDC(hdcMem);
|
||||
hdcMem = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 选择 DIB 到 DC
|
||||
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmDIB);
|
||||
if (hbmOld) {
|
||||
// 如果旧位图非空,需跟踪以便后续删除 (此处可忽略,因为创建时通常为空)
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放所有资源
|
||||
*/
|
||||
void Release() noexcept {
|
||||
if (hbmDIB) {
|
||||
DeleteObject(hbmDIB);
|
||||
hbmDIB = nullptr;
|
||||
}
|
||||
if (hdcMem) {
|
||||
DeleteDC(hdcMem);
|
||||
hdcMem = nullptr;
|
||||
}
|
||||
pBits = nullptr;
|
||||
width = height = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除矩形区域为透明 (所有像素 alpha = 0)
|
||||
*/
|
||||
void ClearRect(const RECT& rc) noexcept {
|
||||
if (!pBits || width == 0 || height == 0) return;
|
||||
|
||||
int left = std::max(0L, rc.left);
|
||||
int top = std::max(0L, rc.top);
|
||||
int right = std::min((LONG)width, rc.right);
|
||||
int bottom = std::min((LONG)height, rc.bottom);
|
||||
|
||||
for (int y = top; y < bottom; ++y) {
|
||||
for (int x = left; x < right; ++x) {
|
||||
pBits[y * width + x] = 0; // ARGB: alpha=0 (完全透明)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指针到特定像素
|
||||
*/
|
||||
inline uint32_t* GetPixel(int x, int y) noexcept {
|
||||
if (x < 0 || x >= (int)width || y < 0 || y >= (int)height) {
|
||||
return nullptr;
|
||||
}
|
||||
return &pBits[y * width + x];
|
||||
}
|
||||
|
||||
~DIBBuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
// 禁用拷贝
|
||||
DIBBuffer(const DIBBuffer&) = delete;
|
||||
DIBBuffer& operator=(const DIBBuffer&) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 单个波纹动画状态
|
||||
*/
|
||||
struct RippleState {
|
||||
int32_t centerX = 0;
|
||||
int32_t centerY = 0;
|
||||
uint64_t startTime = 0; // QueryPerformanceCounter() 返回值
|
||||
|
||||
/**
|
||||
* @brief 计算当前半径
|
||||
* @param nowQPC 当前 QueryPerformanceCounter() 返回值
|
||||
* @param freq QueryPerformanceFrequency() 返回值
|
||||
* @return 半径 (像素),如果波纹已死亡返回 -1.0f
|
||||
*/
|
||||
float GetCurrentRadius(uint64_t nowQPC,
|
||||
uint64_t freq,
|
||||
float maxRadius,
|
||||
uint32_t durationMS,
|
||||
float radiusExponent = 1.0f) const noexcept {
|
||||
if (freq == 0) return -1.0f;
|
||||
|
||||
uint64_t elapsedMS = (nowQPC - startTime) * 1000 / freq;
|
||||
if (elapsedMS > durationMS) {
|
||||
return -1.0f; // 标记已死亡
|
||||
}
|
||||
|
||||
float t = elapsedMS / static_cast<float>(durationMS);
|
||||
float exp = std::max(0.1f, radiusExponent);
|
||||
return maxRadius * std::pow(t, exp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算当前 Alpha 值 (0-255)
|
||||
* 使用立方衰减: alpha = (1 - t)^3
|
||||
*/
|
||||
uint8_t GetCurrentAlpha(uint64_t nowQPC,
|
||||
uint64_t freq,
|
||||
uint32_t durationMS,
|
||||
float alphaExponent = 3.0f) const noexcept {
|
||||
if (freq == 0) return 0;
|
||||
|
||||
uint64_t elapsedMS = (nowQPC - startTime) * 1000 / freq;
|
||||
if (elapsedMS > durationMS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float t = elapsedMS / static_cast<float>(durationMS);
|
||||
float exp = std::max(0.1f, alphaExponent);
|
||||
float alpha_norm = std::pow((1.0f - t), exp);
|
||||
return static_cast<uint8_t>(255.0f * alpha_norm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查波纹是否已鼓励 (可回收)
|
||||
*/
|
||||
bool IsAlive(uint64_t nowQPC, uint64_t freq, uint32_t durationMS) const noexcept {
|
||||
if (freq == 0) return false;
|
||||
uint64_t elapsedMS = (nowQPC - startTime) * 1000 / freq;
|
||||
return elapsedMS <= durationMS;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 波纹对象池
|
||||
*/
|
||||
struct RipplePool {
|
||||
static constexpr size_t MAX_RIPPLES = 12;
|
||||
|
||||
std::array<RippleState, MAX_RIPPLES> ripples{};
|
||||
uint8_t activeCount = 0; // 当前活跃波纹数 (0 到 MAX_RIPPLES)
|
||||
|
||||
/**
|
||||
* @brief 添加新波纹
|
||||
* @return true 成功,false 池已满
|
||||
*/
|
||||
bool AddRipple(int32_t x, int32_t y, uint64_t nowQPC) noexcept {
|
||||
if (activeCount >= MAX_RIPPLES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ripples[activeCount].centerX = x;
|
||||
ripples[activeCount].centerY = y;
|
||||
ripples[activeCount].startTime = nowQPC;
|
||||
activeCount++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清理并压缩死亡波纹,更新 activeCount
|
||||
*/
|
||||
void CompactDeadRipples(uint64_t nowQPC, uint64_t freq, uint32_t durationMS) noexcept {
|
||||
uint8_t writeIdx = 0;
|
||||
for (uint8_t i = 0; i < activeCount; ++i) {
|
||||
if (ripples[i].IsAlive(nowQPC, freq, durationMS)) {
|
||||
ripples[writeIdx++] = ripples[i];
|
||||
}
|
||||
}
|
||||
activeCount = writeIdx;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 脏矩形追踪器
|
||||
*
|
||||
* 记录"上一帧圆圈的包围盒 + 当前帧圆圈的包围盒",
|
||||
* 计算联合区域以确定必须重绘的区域
|
||||
*/
|
||||
class DirtyRectTracker {
|
||||
public:
|
||||
RECT currentDirty{0, 0, 0, 0}; // 当前帧脏区
|
||||
RECT prevDirty{0, 0, 0, 0}; // 上一帧脏区
|
||||
|
||||
uint32_t screenWidth = 1920;
|
||||
uint32_t screenHeight = 1080;
|
||||
|
||||
/**
|
||||
* @brief 记下一个圆形的包围盒到脏区
|
||||
*/
|
||||
void UnionCircle(int x, int y, float radius) noexcept {
|
||||
int l = std::max(0, static_cast<int>(x - radius - 2.0f));
|
||||
int t = std::max(0, static_cast<int>(y - radius - 2.0f));
|
||||
int r = std::min(static_cast<int>(screenWidth),
|
||||
static_cast<int>(x + radius + 2.0f));
|
||||
int b = std::min(static_cast<int>(screenHeight),
|
||||
static_cast<int>(y + radius + 2.0f));
|
||||
|
||||
if (l >= r || t >= b) return; // 无效矩形
|
||||
|
||||
if (currentDirty.left == 0 && currentDirty.right == 0) {
|
||||
currentDirty = {l, t, r, b};
|
||||
} else {
|
||||
currentDirty.left = std::min(currentDirty.left, (LONG)l);
|
||||
currentDirty.top = std::min(currentDirty.top, (LONG)t);
|
||||
currentDirty.right = std::max(currentDirty.right, (LONG)r);
|
||||
currentDirty.bottom = std::max(currentDirty.bottom, (LONG)b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取需要更新的矩形 (当前 + 前一帧的并集)
|
||||
*/
|
||||
RECT GetUpdateRect() const noexcept {
|
||||
RECT result = currentDirty;
|
||||
result.left = std::min(result.left, prevDirty.left);
|
||||
result.top = std::min(result.top, prevDirty.top);
|
||||
result.right = std::max(result.right, prevDirty.right);
|
||||
result.bottom = std::max(result.bottom, prevDirty.bottom);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 循环开始 - 交换脏区,准备新一帧
|
||||
*/
|
||||
void BeginFrame() noexcept {
|
||||
prevDirty = currentDirty;
|
||||
currentDirty = {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查此帧是否有脏内容
|
||||
*/
|
||||
bool HasDirtyRects() const noexcept {
|
||||
return !IsRectEmpty(¤tDirty);
|
||||
}
|
||||
};
|
||||
271
include/MouseHighlighter.h
Normal file
271
include/MouseHighlighter.h
Normal file
@@ -0,0 +1,271 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include "SharedState.h"
|
||||
#include "DataStructures.h"
|
||||
#include "Config.h"
|
||||
|
||||
/**
|
||||
* @brief 鼠标高亮工具应用主类
|
||||
*
|
||||
* 职责:
|
||||
* 1. 创建并管理全屏透明分层窗口
|
||||
* 2. 注册全局低级鼠标钩子(WH_MOUSE_LL)
|
||||
* 3. 启动渲染线程与监控线程
|
||||
* 4. 处理退出信号与资源清理
|
||||
*/
|
||||
class MouseHighlighter {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数 - 初始化默认值
|
||||
*/
|
||||
MouseHighlighter() = default;
|
||||
|
||||
/**
|
||||
* @brief 析构函数 - 清理资源 (若未主动调用 Cleanup)
|
||||
*/
|
||||
~MouseHighlighter();
|
||||
|
||||
/**
|
||||
* @brief 初始化应用
|
||||
*
|
||||
* 执行步骤:
|
||||
* 1. 加载配置文件 (config.ini)
|
||||
* 2. 创建透明分层窗口
|
||||
* 3. 初始化 DIB 双缓冲
|
||||
* 4. 注册全局鼠标钩子
|
||||
* 5. 启动渲染线程 & 监控线程
|
||||
* 6. 创建系统托盘图标
|
||||
*
|
||||
* @return true 初始化成功,false 失败
|
||||
*/
|
||||
bool Initialize();
|
||||
|
||||
/**
|
||||
* @brief 运行消息循环 (阻塞至退出信号)
|
||||
*
|
||||
* @return 消息循环的退出代码
|
||||
*/
|
||||
int Run();
|
||||
|
||||
/**
|
||||
* @brief 请求干净退出
|
||||
*
|
||||
* 这会设置 exitEvent,触发消息循环结束、线程停止、资源清理
|
||||
*/
|
||||
void RequestShutdown();
|
||||
|
||||
/**
|
||||
* @brief 手动清理所有资源
|
||||
*
|
||||
* 可在 WM_DESTROY 或程序结束时调用
|
||||
* 设计为幂等的(可多次调用)
|
||||
*/
|
||||
void Cleanup();
|
||||
|
||||
/**
|
||||
* @brief 获取窗口句柄
|
||||
*/
|
||||
HWND GetHWND() const { return hLayeredWindow; }
|
||||
|
||||
/**
|
||||
* @brief 获取配置对象 (const)
|
||||
*/
|
||||
const Config& GetConfig() const { return config; }
|
||||
|
||||
/**
|
||||
* @brief 获取配置对象 (mutable)
|
||||
*/
|
||||
Config& GetMutableConfig() { return config; }
|
||||
|
||||
/**
|
||||
* @brief 应用新配置 (例如颜色变更、圆形大小变更)
|
||||
*
|
||||
* @param newConfig 新的配置
|
||||
* @return true 成功应用,false 失败
|
||||
*/
|
||||
bool ApplyConfig(const Config& newConfig);
|
||||
|
||||
// 禁用拷贝与移动
|
||||
MouseHighlighter(const MouseHighlighter&) = delete;
|
||||
MouseHighlighter& operator=(const MouseHighlighter&) = delete;
|
||||
MouseHighlighter(MouseHighlighter&&) = delete;
|
||||
MouseHighlighter& operator=(MouseHighlighter&&) = delete;
|
||||
|
||||
private:
|
||||
// ===== 窗口 & 显示相关 =====
|
||||
HWND hLayeredWindow = nullptr; // 透明分层窗口
|
||||
HINSTANCE hInstance = nullptr; // 应用实例句柄
|
||||
|
||||
// ===== 配置 =====
|
||||
Config config;
|
||||
wchar_t configFilePath[MAX_PATH] = {}; // config.ini 路径
|
||||
|
||||
// ===== 图形渲染相关 =====
|
||||
DIBBuffer dibBuffer; // DIB 双缓冲
|
||||
DirtyRectTracker dirtyRectTracker; // 脏矩形追踪
|
||||
LARGE_INTEGER qpcFrequency{}; // QueryPerformanceCounter 频率
|
||||
|
||||
// ===== 共享状态 =====
|
||||
SharedMouseState sharedState; // 钩子 <-> 渲染线程间的共享状态
|
||||
RipplePool ripplePool; // 波纹对象池
|
||||
|
||||
// ===== 线程管理 =====
|
||||
HANDLE hRenderThread = nullptr; // 渲染节拍线程
|
||||
HANDLE hMonitorThread = nullptr; // 性能监控线程
|
||||
HANDLE hExitEvent = nullptr; // 应用退出信号
|
||||
|
||||
// ===== 钩子 =====
|
||||
HHOOK hMouseHook = nullptr; // 全局鼠标钩子
|
||||
|
||||
// ===== 托盘相关 =====
|
||||
NOTIFYICONDATA nid{}; // 托盘图标数据
|
||||
UINT wmTaskbarCreated = 0; // 任务栏重创建消息 ID
|
||||
|
||||
// ===== 平滑坐标 (EMA) =====
|
||||
float smoothedX = 0.0f;
|
||||
float smoothedY = 0.0f;
|
||||
|
||||
// ===== 窗口类名 =====
|
||||
static constexpr wchar_t WINDOW_CLASS_NAME[] = L"MouseHighlightWindow";
|
||||
static constexpr wchar_t APP_NAME[] = L"MouseHighlighter";
|
||||
|
||||
// ===== 内部方法 =====
|
||||
|
||||
/**
|
||||
* @brief 创建透明分层窗口
|
||||
*/
|
||||
bool CreateLayeredWindow();
|
||||
|
||||
/**
|
||||
* @brief 销毁窗口与相关资源
|
||||
*/
|
||||
void DestroyLayeredWindow();
|
||||
|
||||
/**
|
||||
* @brief 初始化 DIB 缓冲
|
||||
*/
|
||||
bool InitializeDIBBuffer();
|
||||
|
||||
/**
|
||||
* @brief 注册全局鼠标钩子
|
||||
*/
|
||||
bool RegisterMouseHook();
|
||||
|
||||
/**
|
||||
* @brief 卸载鼠标钩子
|
||||
*/
|
||||
bool UnregisterMouseHook();
|
||||
|
||||
/**
|
||||
* @brief 启动渲染线程
|
||||
*/
|
||||
bool StartRenderThread();
|
||||
|
||||
/**
|
||||
* @brief 启动监控线程
|
||||
*/
|
||||
bool StartMonitorThread();
|
||||
|
||||
/**
|
||||
* @brief 等待所有线程结束
|
||||
*/
|
||||
void WaitForAllThreads();
|
||||
|
||||
/**
|
||||
* @brief 加载配置文件
|
||||
*/
|
||||
bool LoadConfigFile();
|
||||
|
||||
/**
|
||||
* @brief 保存配置文件
|
||||
*/
|
||||
bool SaveConfigFile();
|
||||
|
||||
/**
|
||||
* @brief 更新 EMA 平滑坐标
|
||||
*/
|
||||
void UpdateSmoothedCursor();
|
||||
|
||||
/**
|
||||
* @brief 执行单次渲染帧
|
||||
*/
|
||||
void RenderFrame();
|
||||
|
||||
/**
|
||||
* @brief 绘制鼠标圆圈光晕到 DIB
|
||||
*/
|
||||
void DrawHalo(int centerX, int centerY);
|
||||
|
||||
/**
|
||||
* @brief 绘制所有活跃波纹到 DIB
|
||||
*/
|
||||
void DrawRipples();
|
||||
|
||||
/**
|
||||
* @brief 更新分层窗口 (输出脏矩形)
|
||||
*/
|
||||
void UpdateLayeredWindowOutput(const RECT& updateRect);
|
||||
|
||||
/**
|
||||
* @brief 创建系统托盘图标与菜单
|
||||
*/
|
||||
bool SetupTrayIcon();
|
||||
|
||||
/**
|
||||
* @brief 销毁托盘图标
|
||||
*/
|
||||
void RemoveTrayIcon();
|
||||
|
||||
/**
|
||||
* @brief 显示托盘右键菜单
|
||||
*/
|
||||
void ShowTrayMenu();
|
||||
|
||||
// ===== 消息处理 =====
|
||||
|
||||
/**
|
||||
* @brief 窗口消息处理函数
|
||||
*/
|
||||
LRESULT HandleWindowMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
/**
|
||||
* @brief 处理托盘消息
|
||||
*/
|
||||
void HandleTrayMessage(UINT msg, int iconID);
|
||||
|
||||
// ===== 全局钩子回调 (静态) =====
|
||||
|
||||
/**
|
||||
* @brief 全局鼠标钩子回调 (低级)
|
||||
*
|
||||
* 由 Windows 直接调用,必须快 (< 50μs)
|
||||
*/
|
||||
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
// ===== 线程函数 (静态) =====
|
||||
|
||||
/**
|
||||
* @brief 渲染节拍线程
|
||||
*/
|
||||
static DWORD WINAPI RenderTickThreadProc(LPVOID lpParam);
|
||||
|
||||
/**
|
||||
* @brief 监控线程
|
||||
*/
|
||||
static DWORD WINAPI MonitorThreadProc(LPVOID lpParam);
|
||||
|
||||
// ===== 窗口过程 (静态) =====
|
||||
|
||||
/**
|
||||
* @brief 窗口过程回调
|
||||
*/
|
||||
static LRESULT CALLBACK WindowProcStatic(HWND hWnd, UINT msg,
|
||||
WPARAM wParam, LPARAM lParam);
|
||||
|
||||
// ===== 全局实例指针 (用于静态回调) =====
|
||||
|
||||
static MouseHighlighter* s_pInstance;
|
||||
};
|
||||
96
include/SharedState.h
Normal file
96
include/SharedState.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <windows.h>
|
||||
|
||||
// 对齐至缓存行 (64 字节) 避免伪共享
|
||||
#pragma pack(push, 8)
|
||||
|
||||
/**
|
||||
* @brief 点击事件结构 - 无锁环形队列中的单个元素
|
||||
*/
|
||||
struct ClickEvent {
|
||||
int32_t x, y; // 屏幕坐标
|
||||
uint32_t timestamp; // GetTickCount64() 毫秒戳
|
||||
uint8_t button; // 0=Left, 1=Right, 2=Middle
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 钩子线程与渲染线程间的共享状态
|
||||
*
|
||||
* - 钩子线程写入:cursorX, cursorY, isDirty, clickQueue
|
||||
* - 渲染线程读取:同上
|
||||
* - 所有操作无锁,仅原子变量
|
||||
*/
|
||||
struct alignas(64) SharedMouseState {
|
||||
// 鼠标实时坐标 - 钩子线程写,渲染线程读
|
||||
std::atomic<int32_t> cursorX{0};
|
||||
std::atomic<int32_t> cursorY{0};
|
||||
|
||||
// 脏标记 - 通知渲染线程需要更新
|
||||
std::atomic<bool> isDirty{false};
|
||||
|
||||
// 点击事件环形队列
|
||||
static constexpr size_t MAX_CLICK_EVENTS = 8;
|
||||
std::array<ClickEvent, MAX_CLICK_EVENTS> clickQueue{};
|
||||
|
||||
// 环形队列指针 - 只能单调递增
|
||||
// 注意: 不追回溯,无需 CAS;仅需 load/store 序列化
|
||||
std::atomic<uint8_t> clickHead{0}; // 写指针 (钩子线程写)
|
||||
std::atomic<uint8_t> clickTail{0}; // 读指针 (渲染线程读)
|
||||
|
||||
/**
|
||||
* @brief 钩子线程调用 - 尝试入队一个点击事件
|
||||
* @return true 成功入队,false 队列满
|
||||
*/
|
||||
bool TryEnqueueClick(int32_t x, int32_t y, uint8_t button) noexcept {
|
||||
uint8_t head = clickHead.load(std::memory_order_relaxed);
|
||||
uint8_t tail = clickTail.load(std::memory_order_acquire);
|
||||
|
||||
// 检查队列是否满
|
||||
uint8_t nextHead = (head + 1) % MAX_CLICK_EVENTS;
|
||||
if (nextHead == tail) {
|
||||
return false; // 队列满,丢弃
|
||||
}
|
||||
|
||||
// 写入事件
|
||||
clickQueue[head].x = x;
|
||||
clickQueue[head].y = y;
|
||||
clickQueue[head].timestamp = GetTickCount();
|
||||
clickQueue[head].button = button;
|
||||
|
||||
// 推进写指针
|
||||
clickHead.store(nextHead, std::memory_order_release);
|
||||
isDirty.store(true, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染线程调用 - 尝试出队一个点击事件
|
||||
* @return true 成功出队到 event,false 队列空
|
||||
*/
|
||||
bool TryDequeueClick(ClickEvent& event) noexcept {
|
||||
uint8_t head = clickHead.load(std::memory_order_acquire);
|
||||
uint8_t tail = clickTail.load(std::memory_order_relaxed);
|
||||
|
||||
if (tail == head) {
|
||||
return false; // 队列空
|
||||
}
|
||||
|
||||
// 读取事件
|
||||
event = clickQueue[tail];
|
||||
|
||||
// 推进读指针
|
||||
clickTail.store((tail + 1) % MAX_CLICK_EVENTS, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
// 编译期检查
|
||||
static_assert(sizeof(SharedMouseState) <= 256, "SharedMouseState should not exceed 256 bytes");
|
||||
static_assert(alignof(SharedMouseState) == 64, "SharedMouseState must be cache-line aligned");
|
||||
Reference in New Issue
Block a user