241 lines
7.8 KiB
C++
241 lines
7.8 KiB
C++
#include "Draw.h"
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include "../../Backend/Config/Config.h"
|
|
#include "../../Backend/Misc/Fonts/FontManager.h"
|
|
#include "../Renderer/Renderer.h"
|
|
#include <psapi.h>
|
|
|
|
CConfig* cfg = CConfig::get();
|
|
|
|
static std::time_t GetSystemTimeRaw()
|
|
{
|
|
auto now = std::chrono::system_clock::now();
|
|
return std::chrono::system_clock::to_time_t(now);
|
|
}
|
|
|
|
static std::tm GetSystemTimeLocal()
|
|
{
|
|
std::time_t now = GetSystemTimeRaw();
|
|
std::tm localTime;
|
|
localtime_s(&localTime, &now);
|
|
return localTime;
|
|
}
|
|
|
|
static std::string GetSystemTimeString(const char* format)
|
|
{
|
|
std::tm localTime = GetSystemTimeLocal();
|
|
std::stringstream ss;
|
|
ss << std::put_time(&localTime, format);
|
|
return ss.str();
|
|
}
|
|
|
|
static void Time() {
|
|
if (!cfg->b["time"])
|
|
return;
|
|
|
|
// 时间格式: 0-3 标准(前导零), 4-7 短格式(所有两位数字去前导零)
|
|
static const char* baseFormats[] = { "%H:%M:%S", "%H:%M", "%I:%M:%S %p", "%I:%M %p" };
|
|
int fmtIdx = cfg->i.count("time_format") ? std::clamp(cfg->i["time_format"], 0, 7) : 0;
|
|
bool shortFormat = (fmtIdx >= 4);
|
|
std::string time = GetSystemTimeString(baseFormats[shortFormat ? (fmtIdx - 4) : fmtIdx]);
|
|
|
|
// 短格式:去除所有两位数字的前导零 "01:02:03" → "1:2:3"
|
|
if (shortFormat)
|
|
{
|
|
std::string out;
|
|
for (size_t i = 0; i < time.size(); i++)
|
|
{
|
|
if (time[i] == '0' && i + 1 < time.size()
|
|
&& time[i + 1] >= '0' && time[i + 1] <= '9'
|
|
&& (i == 0 || time[i - 1] < '0' || time[i - 1] > '9'))
|
|
{
|
|
continue; // skip leading zero
|
|
}
|
|
out += time[i];
|
|
}
|
|
time = out;
|
|
}
|
|
|
|
// 分离主时间部分与 AM/PM 后缀
|
|
std::string timeMain = time;
|
|
std::string timeSuffix;
|
|
const char* ampmMarkers[] = { " AM", " PM" };
|
|
for (int mi = 0; mi < 2; mi++)
|
|
{
|
|
size_t pos = time.rfind(ampmMarkers[mi]);
|
|
if (pos != std::string::npos)
|
|
{
|
|
timeMain = time.substr(0, pos);
|
|
timeSuffix = ampmMarkers[mi];
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto& fm = FontManager::GetInstance();
|
|
|
|
// 注册主字体
|
|
int fontSize = cfg->i["time_text_size"];
|
|
if (fontSize <= 0) fontSize = 45;
|
|
|
|
static int lastFontSize = 0;
|
|
if (lastFontSize != fontSize) {
|
|
if (lastFontSize == 0)
|
|
fm.RegisterFont("TimeFont", "C:\\Windows\\Fonts\\Verdana.ttf", fontSize);
|
|
else
|
|
fm.SetFontBaseSize("TimeFont", fontSize);
|
|
lastFontSize = fontSize;
|
|
}
|
|
fm.EnsureText("TimeFont", timeMain.c_str());
|
|
RLFont font = fm.GetFont("TimeFont");
|
|
float charSpacing = (float)cfg->i["time_text_character_spacing"];
|
|
|
|
// 注册 AM/PM 小字体(主字体 ~55% 大小)
|
|
RLFont smallFont = { 0 };
|
|
int suffixFontSize = 0;
|
|
float suffixOffsetX = 0.f;
|
|
if (!timeSuffix.empty())
|
|
{
|
|
suffixFontSize = std::max(8, (int)(fontSize * 0.55f));
|
|
static int lastSuffixSize = 0;
|
|
if (lastSuffixSize != suffixFontSize) {
|
|
if (lastSuffixSize == 0)
|
|
fm.RegisterFont("TimeFontSmall", "C:\\Windows\\Fonts\\Verdana.ttf", suffixFontSize);
|
|
else
|
|
fm.SetFontBaseSize("TimeFontSmall", suffixFontSize);
|
|
lastSuffixSize = suffixFontSize;
|
|
}
|
|
fm.EnsureText("TimeFontSmall", timeSuffix.c_str());
|
|
smallFont = fm.GetFont("TimeFontSmall");
|
|
// 后缀 X 偏移 = 主时间文本宽度 + 字符间距
|
|
RLVector2 mainSize = fm.MeasureText("TimeFont", timeMain.c_str(), fontSize);
|
|
suffixOffsetX = mainSize.x + charSpacing;
|
|
}
|
|
|
|
const int* color = cfg->c["time_text_Color"];
|
|
RLColor textColor = {
|
|
static_cast<unsigned char>(color[0]),
|
|
static_cast<unsigned char>(color[1]),
|
|
static_cast<unsigned char>(color[2]),
|
|
static_cast<unsigned char>(color[3])
|
|
};
|
|
|
|
float xPos = cfg->f["time_x_pos"];
|
|
float yPos = cfg->f["time_y_pos"];
|
|
// AM/PM 垂直偏下偏移靠左
|
|
float suffixOffsetY = timeSuffix.empty() ? 0.f : ((float)fontSize - (float)suffixFontSize) * 0.65f;
|
|
|
|
const int* colorborder = cfg->c["text_border_Color"];
|
|
RLColor borderCol = {
|
|
static_cast<unsigned char>(colorborder[0]),
|
|
static_cast<unsigned char>(colorborder[1]),
|
|
static_cast<unsigned char>(colorborder[2]),
|
|
static_cast<unsigned char>(colorborder[3])
|
|
};
|
|
if (cfg->b["text_border"]) {
|
|
for (int dy = -1; dy <= 1; ++dy) {
|
|
for (int dx = -1; dx <= 1; ++dx) {
|
|
if (dx == 0 && dy == 0) continue;
|
|
RLDrawTextEx(font, timeMain.c_str(), RLVector2{ xPos + (float)dx, yPos + (float)dy }, (float)fontSize, charSpacing, borderCol);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
RLDrawTextEx(font, timeMain.c_str(), RLVector2{ xPos, yPos }, (float)fontSize, charSpacing, textColor);
|
|
}
|
|
|
|
static std::string GetForegroundProcessName() {
|
|
HWND fg = GetForegroundWindow();
|
|
if (!fg) return "";
|
|
DWORD pid = 0;
|
|
GetWindowThreadProcessId(fg, &pid);
|
|
if (pid == 0) return "";
|
|
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
|
if (!hProc) return "";
|
|
char exeName[MAX_PATH] = "";
|
|
DWORD size = MAX_PATH;
|
|
if (!QueryFullProcessImageNameA(hProc, 0, exeName, &size)) {
|
|
CloseHandle(hProc);
|
|
return "";
|
|
}
|
|
CloseHandle(hProc);
|
|
const char* base = strrchr(exeName, '\\');
|
|
return base ? (base + 1) : exeName;
|
|
}
|
|
|
|
static bool IsWindowWhitelisted() {
|
|
std::string whitelist(cfg->s["keyboard_whitelist"]);
|
|
if (whitelist.empty()) return false;
|
|
|
|
std::string fgName = GetForegroundProcessName();
|
|
if (fgName.empty()) return false;
|
|
|
|
// Case-insensitive exact match against whitelist entries
|
|
size_t pos = 0;
|
|
while (pos < whitelist.size()) {
|
|
size_t end = whitelist.find(';', pos);
|
|
if (end == std::string::npos) end = whitelist.size();
|
|
std::string entry = whitelist.substr(pos, end - pos);
|
|
if (!entry.empty() && _stricmp(entry.c_str(), fgName.c_str()) == 0)
|
|
return true;
|
|
pos = end + 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void KeyboardControl() {
|
|
if (!cfg->b["keyboard_control"])
|
|
return;
|
|
|
|
// Skip control if active window is in whitelist
|
|
if (IsWindowWhitelisted())
|
|
return;
|
|
|
|
// Throttle: only attempt toggle every 250ms to prevent frame-rate flicker
|
|
// when the game also fights for lock-key state
|
|
static ULONGLONG lastAttempt = 0;
|
|
ULONGLONG now = GetTickCount64();
|
|
if (now - lastAttempt < 250)
|
|
return;
|
|
lastAttempt = now;
|
|
|
|
// Caps Lock control
|
|
{
|
|
int mode = cfg->i["Caps_control"];
|
|
if (mode != 0) {
|
|
bool capsOn = (GetKeyState(VK_CAPITAL) & 0x0001) != 0;
|
|
bool wantOn = (mode == 1); // 1=常开(Always On), 2=常关(Always Off)
|
|
if (capsOn != wantOn) {
|
|
// keybd_event is NOT subject to UIPI (unlike SendInput), so it works with elevated fullscreen games
|
|
keybd_event(VK_CAPITAL, 0x3A, 0, 0);
|
|
keybd_event(VK_CAPITAL, 0x3A, KEYEVENTF_KEYUP, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Num Lock control
|
|
{
|
|
int mode = cfg->i["Num_control"];
|
|
if (mode != 0) {
|
|
bool numOn = (GetKeyState(VK_NUMLOCK) & 0x0001) != 0;
|
|
bool wantOn = (mode == 1); // 1=常开(Always On), 2=常关(Always Off)
|
|
if (numOn != wantOn) {
|
|
// keybd_event is NOT subject to UIPI (unlike SendInput), so it works with elevated fullscreen games
|
|
keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
|
|
keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace Render {
|
|
|
|
void DrawOverlay() {
|
|
KeyboardControl();
|
|
Time();
|
|
}
|
|
|
|
} // namespace Render
|