/
devofthefour
/
ITPractice
Обзор
Документация
Войти
/
devofthefour
/
ITPractice
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/main.cpp
1 594 строки
58 KB
devofthefour
update src/main.cpp
25 дек 2025, 14:53
25 дек 2025, 14:53
9ced293
Код
Авторство
О чём код?
#ifndef UNICODE #define UNICODE #endif #ifndef _UNICODE #define _UNICODE #endif #include <windows.h> #include <windowsx.h> #include <Psapi.h> #include <Pdh.h> #include <PdhMsg.h> #include <stdio.h> #include <string> #include <vector> #include <deque> #include <algorithm> #include <cmath> #include <iostream> #include <shellapi.h> #include <fstream> #include <sstream> #include <iomanip> #include <ctime> #include <locale> #include <codecvt> #include <chrono> #include "ShlObj.h" #include "util.hpp" #ifdef TESTS #include "./test/unit_tests.hpp" #include "./test/integration_tests.hpp" #endif #pragma comment(lib, "pdh.lib") #pragma comment(lib, "psapi.lib") #pragma comment(lib, "shell32.lib") HWND g_hwnd; SYSTEM_INFO g_sysInfo; MEMORYSTATUSEX g_memStatus; ULARGE_INTEGER g_lastIdleTime, g_lastKernelTime, g_lastUserTime; MetricHistory g_history; std::vector<DiskInfo> g_disksInfo; // Глобальные состояния bool g_isPaused = false; bool g_showControls = true; bool g_isMinimizedToTray = false; bool g_showCpuInTray = true; UINT_PTR g_updateTimer = 0; // ID иконки в трее #define ID_TRAY_ICON 100 #define WM_TRAYICON (WM_USER + 1) // Идентификаторы пунктов меню трея #define ID_TRAY_SHOW 101 #define ID_TRAY_HIDE 102 #define ID_TRAY_PAUSE 103 #define ID_TRAY_EXIT 104 #define ID_TRAY_CPU_DISPLAY 105 #define ID_TRAY_EXPORT_CSV 106 // Идентификаторы для экспорта #define ID_EXPORT_CURRENT 107 #define ID_EXPORT_ALL_HISTORY 108 // Цвета для графиков (яркие на тёмном фоне) COLORREF g_chartColors[] = { RGB(66, 133, 244), // CPU - ярко-синий RGB(52, 168, 83), // RAM - ярко-зеленый RGB(255, 109, 0), // Virtual - оранжевый RGB(156, 39, 176), // Disk 1 - фиолетовый RGB(255, 87, 34), // Disk 2 - красный/оранжевый RGB(63, 81, 181), // Disk 3 - индиго RGB(76, 175, 80) // Disk 4 - зеленый }; // Тёмная цветовая схема namespace DarkTheme { const COLORREF Background = RGB(18, 18, 18); const COLORREF Panel = RGB(30, 30, 30); const COLORREF Border = RGB(55, 55, 55); const COLORREF Text = RGB(240, 240, 240); const COLORREF TextSecondary = RGB(180, 180, 180); const COLORREF Grid = RGB(45, 45, 45); const COLORREF ProgressBackground = RGB(40, 40, 40); const COLORREF ButtonNormal = RGB(66, 133, 244); const COLORREF ButtonHover = RGB(86, 153, 264); const COLORREF ButtonPressed = RGB(46, 113, 224); const COLORREF ButtonDisabled = RGB(80, 80, 80); const COLORREF PauseIndicator = RGB(255, 87, 34); const COLORREF TrayCpuLow = RGB(52, 168, 83); // Зеленый для низкой загрузки const COLORREF TrayCpuMedium = RGB(255, 193, 7); // Желтый для средней const COLORREF TrayCpuHigh = RGB(220, 53, 69); // Красный для высокой const COLORREF ExportButton = RGB(76, 175, 80); // Зеленый для кнопки экспорта } // Получение загрузки CPU double GetCpuUsage() { FILETIME idleTime, kernelTime, userTime; if (!GetSystemTimes(&idleTime, &kernelTime, &userTime)) { return 0.0; } ULARGE_INTEGER idle, kernel, user; idle.LowPart = idleTime.dwLowDateTime; idle.HighPart = idleTime.dwHighDateTime; kernel.LowPart = kernelTime.dwLowDateTime; kernel.HighPart = kernelTime.dwHighDateTime; user.LowPart = userTime.dwLowDateTime; user.HighPart = userTime.dwHighDateTime; ULONGLONG sys = (kernel.QuadPart - g_lastKernelTime.QuadPart) + (user.QuadPart - g_lastUserTime.QuadPart); ULONGLONG idleDiff = idle.QuadPart - g_lastIdleTime.QuadPart; g_lastIdleTime = idle; g_lastKernelTime = kernel; g_lastUserTime = user; if (sys == 0) return 0.0; return 100.0 * (sys - idleDiff) / sys; } // Получение информации о памяти void GetMemoryUsage(DWORDLONG& totalPhys, DWORDLONG& usedPhys, DWORDLONG& totalVirtMem, DWORDLONG& usedVirtMem) { g_memStatus.dwLength = sizeof(g_memStatus); GlobalMemoryStatusEx(&g_memStatus); // Физическая память totalPhys = g_memStatus.ullTotalPhys; usedPhys = g_memStatus.ullTotalPhys - g_memStatus.ullAvailPhys; // Виртуальная память totalVirtMem = g_memStatus.ullTotalPageFile; usedVirtMem = g_memStatus.ullTotalPageFile - g_memStatus.ullAvailPageFile; // Валидация if (totalVirtMem == 0) { totalVirtMem = 1; // Избегаем деления на ноль } if (usedVirtMem > totalVirtMem) { usedVirtMem = totalVirtMem; } } // Добавление новых значений в историю void UpdateHistory(double cpu, double memory, double virtualMemory, const std::vector<DiskInfo>& disks) { if (g_isPaused) { return; // Не обновляем историю при паузе } // CPU g_history.cpu.push_back(cpu); if (g_history.cpu.size() > MAX_HISTORY) { g_history.cpu.pop_front(); } // RAM g_history.memory.push_back(memory); if (g_history.memory.size() > MAX_HISTORY) { g_history.memory.pop_front(); } // Virtual Memory g_history.virtualMemory.push_back(virtualMemory); if (g_history.virtualMemory.size() > MAX_HISTORY) { g_history.virtualMemory.pop_front(); } // Disks if (g_history.diskUsage.size() != disks.size()) { g_history.diskUsage.resize(disks.size()); g_history.diskNames.clear(); for (const auto& disk : disks) { g_history.diskNames.push_back(disk.name); } } for (size_t i = 0; i < disks.size(); i++) { g_history.diskUsage[i].push_back(disks[i].usage); if (g_history.diskUsage[i].size() > MAX_HISTORY) { g_history.diskUsage[i].pop_front(); } } } // Очистка всех графиков void ClearAllGraphs() { g_history.cpu.clear(); g_history.memory.clear(); g_history.virtualMemory.clear(); for (auto& diskHistory : g_history.diskUsage) { diskHistory.clear(); } // Принудительная перерисовка InvalidateRect(g_hwnd, NULL, TRUE); } // Обновление иконки в трее с текущим процентом CPU void UpdateTrayIcon() { if (!g_showCpuInTray && g_isMinimizedToTray) { // Если отображение CPU отключено, используем статичную иконку NOTIFYICONDATA nid = {}; nid.cbSize = sizeof(NOTIFYICONDATA); nid.hWnd = g_hwnd; nid.uID = ID_TRAY_ICON; nid.uFlags = NIF_ICON | NIF_TIP; HICON hIcon; if (g_isPaused) { hIcon = LoadIcon(NULL, IDI_WARNING); } else { hIcon = LoadIcon(NULL, IDI_APPLICATION); } if (hIcon) { nid.hIcon = hIcon; wchar_t tip[128]; double cpuUsage = 0; if (!g_history.cpu.empty()) { cpuUsage = g_history.cpu.back(); } if (g_isPaused) { swprintf(tip, 128, L"System Monitor [Пауза]\nCPU: %.1f%%", cpuUsage); } else { swprintf(tip, 128, L"System Monitor\nCPU: %.1f%%", cpuUsage); } wcscpy_s(nid.szTip, tip); Shell_NotifyIcon(NIM_MODIFY, &nid); } return; } // Получаем текущую загрузку CPU double cpuUsage = 0; if (!g_history.cpu.empty()) { cpuUsage = g_history.cpu.back(); } // Создаем иконку с процентом CPU HDC hDC = GetDC(NULL); HDC hMemDC = CreateCompatibleDC(hDC); HBITMAP hBitmap = CreateCompatibleBitmap(hDC, 16, 16); HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); // Выбираем цвет в зависимости от загрузки CPU COLORREF cpuColor; if (cpuUsage < 50) { cpuColor = DarkTheme::TrayCpuLow; } else if (cpuUsage < 80) { cpuColor = DarkTheme::TrayCpuMedium; } else { cpuColor = DarkTheme::TrayCpuHigh; } if (g_isPaused) { cpuColor = RGB(128, 128, 128); // Серый при паузе } // Рисуем фон HBRUSH hBgBrush = CreateSolidBrush(RGB(30, 30, 30)); RECT rect = {0, 0, 16, 16}; FillRect(hMemDC, &rect, hBgBrush); DeleteObject(hBgBrush); // Рисуем прогресс-бар CPU if (!g_isPaused) { int cpuWidth = static_cast<int>(16 * cpuUsage / 100.0); if (cpuWidth > 0) { HBRUSH hCpuBrush = CreateSolidBrush(cpuColor); RECT cpuRect = {0, 0, cpuWidth, 16}; FillRect(hMemDC, &cpuRect, hCpuBrush); DeleteObject(hCpuBrush); } } // Рисуем рамку HPEN hBorderPen = CreatePen(PS_SOLID, 1, cpuColor); HPEN hOldPen = (HPEN)SelectObject(hMemDC, hBorderPen); SelectObject(hMemDC, GetStockObject(NULL_BRUSH)); Rectangle(hMemDC, 0, 0, 16, 16); SelectObject(hMemDC, hOldPen); DeleteObject(hBorderPen); // Если CPU выше 99%, рисуем предупреждающий символ if (cpuUsage > 99 && !g_isPaused) { SetBkMode(hMemDC, TRANSPARENT); SetTextColor(hMemDC, RGB(255, 255, 255)); HFONT hFont = CreateFont(10, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Arial"); HFONT hOldFont = (HFONT)SelectObject(hMemDC, hFont); DrawTextW(hMemDC, L"!", 1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hMemDC, hOldFont); DeleteObject(hFont); } // Создаем иконку из bitmap ICONINFO iconInfo = {}; iconInfo.fIcon = TRUE; iconInfo.hbmColor = hBitmap; iconInfo.hbmMask = hBitmap; HICON hIcon = CreateIconIndirect(&iconInfo); // Обновляем иконку в трее NOTIFYICONDATA nid = {}; nid.cbSize = sizeof(NOTIFYICONDATA); nid.hWnd = g_hwnd; nid.uID = ID_TRAY_ICON; nid.uFlags = NIF_ICON | NIF_TIP; nid.hIcon = hIcon; wchar_t tip[128]; if (g_isPaused) { swprintf(tip, 128, L"System Monitor [Пауза]\nCPU: %.1f%%", cpuUsage); } else { swprintf(tip, 128, L"System Monitor\nCPU: %.1f%%", cpuUsage); } wcscpy_s(nid.szTip, tip); Shell_NotifyIcon(NIM_MODIFY, &nid); // Обновляем заголовок окна wchar_t windowTitle[256]; swprintf(windowTitle, 256, L"System Monitor - CPU: %.1f%%", cpuUsage); SetWindowTextW(g_hwnd, windowTitle); // Очистка DestroyIcon(hIcon); SelectObject(hMemDC, hOldBitmap); DeleteObject(hBitmap); DeleteDC(hMemDC); ReleaseDC(NULL, hDC); } // Переключение паузы void TogglePause() { g_isPaused = !g_isPaused; if (g_isPaused) { KillTimer(g_hwnd, ID_TIMER); } else { SetTimer(g_hwnd, ID_TIMER, UPDATE_INTERVAL, NULL); } // Обновляем иконку в трее при паузе UpdateTrayIcon(); InvalidateRect(g_hwnd, NULL, TRUE); } // Экспорт текущих данных в CSV (сохраняет в текущую директорию) bool ExportCurrentDataToCSV() { // Создаем имя файла с текущей датой и временем SYSTEMTIME st; GetLocalTime(&st); wchar_t filename[MAX_PATH]; swprintf(filename, MAX_PATH, L"system_monitor_%04d%02d%02d_%02d%02d%02d.csv", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); // Используем текущую директорию wchar_t currentDir[MAX_PATH]; GetCurrentDirectoryW(MAX_PATH, currentDir); wchar_t fullPath[MAX_PATH]; swprintf(fullPath, MAX_PATH, L"%s\\%s", currentDir, filename); // Преобразуем путь в ANSI для std::ofstream std::string ansiPath = WStringToANSI(fullPath); // Используем ofstream std::ofstream file; file.open(ansiPath, std::ios::binary); if (!file.is_open()) { // Попробуем открыть просто по имени файла (без пути) std::string simpleFilename = WStringToANSI(filename); file.open(simpleFilename, std::ios::binary); if (!file.is_open()) { MessageBoxW(g_hwnd, L"Не удалось создать файл", L"Ошибка экспорта", MB_ICONERROR); return false; } } // Записываем UTF-8 BOM unsigned char bom[] = {0xEF, 0xBB, 0xBF}; file.write(reinterpret_cast<char*>(bom), sizeof(bom)); // Заголовок CSV (используем английские названия для избежания проблем) std::string header = "Time,CPU (%),RAM (%),Pagefile (%)"; // Добавляем заголовки для дисков for (size_t i = 0; i < g_history.diskNames.size(); i++) { header += ",Disk "; header += ('A' + i); header += " (%)"; } header += "\n"; file << header; // Получаем текущие данные double cpuUsage = 0; double memoryPercent = 0; double virtualMemoryPercent = 0; if (!g_history.cpu.empty()) cpuUsage = g_history.cpu.back(); if (!g_history.memory.empty()) memoryPercent = g_history.memory.back(); if (!g_history.virtualMemory.empty()) virtualMemoryPercent = g_history.virtualMemory.back(); // Форматируем время char timeStr[32]; sprintf(timeStr, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond); // Форматируем данные с помощью stringstream std::stringstream dataLine; dataLine << std::fixed << std::setprecision(2); dataLine << timeStr << "," << cpuUsage << "," << memoryPercent << "," << virtualMemoryPercent; // Данные по дискам for (size_t i = 0; i < g_history.diskUsage.size(); i++) { double diskUsage = 0; if (!g_history.diskUsage[i].empty()) { diskUsage = g_history.diskUsage[i].back(); } dataLine << "," << diskUsage; } dataLine << "\n"; file << dataLine.str(); file.close(); wchar_t message[512]; swprintf(message, 512, L"Данные успешно экспортированы в файл:\n%s", filename); MessageBoxW(g_hwnd, message, L"Экспорт завершен", MB_ICONINFORMATION); return true; } // Экспорт всей истории в CSV (сохраняет в текущую директорию) bool ExportAllHistoryToCSV() { if (g_history.cpu.empty()) { MessageBoxW(g_hwnd, L"Нет данных для экспорта", L"Ошибка экспорта", MB_ICONWARNING); return false; } // Создаем имя файла с текущей датой и временем SYSTEMTIME st; GetLocalTime(&st); wchar_t filename[MAX_PATH]; swprintf(filename, MAX_PATH, L"system_monitor_history_%04d%02d%02d_%02d%02d%02d.csv", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); // Используем текущую директорию wchar_t currentDir[MAX_PATH]; GetCurrentDirectoryW(MAX_PATH, currentDir); wchar_t fullPath[MAX_PATH]; swprintf(fullPath, MAX_PATH, L"%s\\%s", currentDir, filename); // Преобразуем путь в ANSI для std::ofstream std::string ansiPath = WStringToANSI(fullPath); // Используем ofstream std::ofstream file; file.open(ansiPath, std::ios::binary); if (!file.is_open()) { // Попробуем открыть просто по имени файла (без пути) std::string simpleFilename = WStringToANSI(filename); file.open(simpleFilename, std::ios::binary); if (!file.is_open()) { MessageBoxW(g_hwnd, L"Не удалось создать файл", L"Ошибка экспорта", MB_ICONERROR); return false; } } // Записываем UTF-8 BOM unsigned char bom[] = {0xEF, 0xBB, 0xBF}; file.write(reinterpret_cast<char*>(bom), sizeof(bom)); // Заголовок CSV (английские названия) std::string header = "Time,CPU (%),RAM (%),Pagefile (%)"; // Заголовки для дисков for (size_t i = 0; i < g_history.diskNames.size(); i++) { header += ",Disk "; header += ('A' + i); header += " (%)"; } header += "\n"; file << header; // Определяем количество записей size_t recordCount = g_history.cpu.size(); recordCount = std::min(recordCount, g_history.memory.size()); recordCount = std::min(recordCount, g_history.virtualMemory.size()); for (size_t i = 0; i < g_history.diskUsage.size(); i++) { recordCount = std::min(recordCount, g_history.diskUsage[i].size()); } // Экспортируем записи в обратном порядке (от старых к новым) for (size_t i = 0; i < recordCount; i++) { // Рассчитываем время для записи int secondsAgo = static_cast<int>(recordCount - i - 1); int hours = secondsAgo / 3600; int minutes = (secondsAgo % 3600) / 60; int seconds = secondsAgo % 60; // Форматируем время char timeStr[32]; if (hours > 0) { sprintf(timeStr, "-%02d:%02d:%02d", hours, minutes, seconds); } else { sprintf(timeStr, "-%02d:%02d", minutes, seconds); } // Форматируем строку данных std::stringstream dataLine; dataLine << std::fixed << std::setprecision(2); dataLine << timeStr << "," << g_history.cpu[i] << "," << g_history.memory[i] << "," << g_history.virtualMemory[i]; // Данные по дискам for (size_t j = 0; j < g_history.diskUsage.size(); j++) { dataLine << "," << g_history.diskUsage[j][i]; } dataLine << "\n"; file << dataLine.str(); } file.close(); wchar_t message[512]; swprintf(message, 512, L"Экспортировано %d записей в файл:\n%s", recordCount, filename); MessageBoxW(g_hwnd, message, L"Экспорт истории завершен", MB_ICONINFORMATION); return true; } // Создание иконки для трея с процентом CPU void CreateTrayIcon(HWND hwnd) { NOTIFYICONDATA nid = {}; nid.cbSize = sizeof(NOTIFYICONDATA); nid.hWnd = hwnd; nid.uID = ID_TRAY_ICON; nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; nid.uCallbackMessage = WM_TRAYICON; // Создаем временную иконку HICON hIcon = LoadIcon(NULL, IDI_APPLICATION); nid.hIcon = hIcon; wcscpy_s(nid.szTip, L"System Monitor\nНажмите для меню"); Shell_NotifyIcon(NIM_ADD, &nid); } // Удаление иконки из трея void RemoveTrayIcon(HWND hwnd) { NOTIFYICONDATA nid = {}; nid.cbSize = sizeof(NOTIFYICONDATA); nid.hWnd = hwnd; nid.uID = ID_TRAY_ICON; Shell_NotifyIcon(NIM_DELETE, &nid); } // Сворачивание окна в трей void MinimizeToTray(HWND hwnd) { ShowWindow(hwnd, SW_HIDE); g_isMinimizedToTray = true; // Обновляем состояние в меню трея UpdateTrayIcon(); } // Восстановление окна из трея void RestoreFromTray(HWND hwnd) { ShowWindow(hwnd, SW_SHOW); ShowWindow(hwnd, SW_RESTORE); SetForegroundWindow(hwnd); g_isMinimizedToTray = false; // Обновляем состояние в меню трея UpdateTrayIcon(); } // Создание контекстного меню для иконки в трее void ShowTrayContextMenu(HWND hwnd, POINT pt) { HMENU hMenu = CreatePopupMenu(); if (g_isMinimizedToTray) { AppendMenuW(hMenu, MF_STRING, ID_TRAY_SHOW, L"Показать окно"); } else { AppendMenuW(hMenu, MF_STRING, ID_TRAY_HIDE, L"Свернуть в трей"); } AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL); if (g_isPaused) { AppendMenuW(hMenu, MF_STRING, ID_TRAY_PAUSE, L"Возобновить мониторинг"); } else { AppendMenuW(hMenu, MF_STRING, ID_TRAY_PAUSE, L"Приостановить мониторинг"); } AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL); // Подменю экспорта HMENU hExportMenu = CreatePopupMenu(); AppendMenuW(hExportMenu, MF_STRING, ID_EXPORT_CURRENT, L"Текущие данные"); AppendMenuW(hExportMenu, MF_STRING, ID_EXPORT_ALL_HISTORY, L"Всю историю"); AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hExportMenu, L"📊 Экспорт в CSV..."); AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL); if (g_showCpuInTray) { AppendMenuW(hMenu, MF_STRING | MF_CHECKED, ID_TRAY_CPU_DISPLAY, L"Показывать CPU в трее"); } else { AppendMenuW(hMenu, MF_STRING, ID_TRAY_CPU_DISPLAY, L"Показывать CPU в трее"); } AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL); AppendMenuW(hMenu, MF_STRING, ID_TRAY_EXIT, L"Выход"); // Позиционируем меню SetForegroundWindow(hwnd); // Отображаем меню UINT uFlags = TPM_RIGHTBUTTON; if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) { uFlags |= TPM_RIGHTALIGN; } else { uFlags |= TPM_LEFTALIGN; } TrackPopupMenuEx(hMenu, uFlags, pt.x, pt.y, hwnd, NULL); // Уничтожаем меню экспорта и главное меню DestroyMenu(hExportMenu); DestroyMenu(hMenu); } // Обработка выбора пункта меню трея void HandleTrayMenuCommand(WPARAM wParam) { switch (LOWORD(wParam)) { case ID_TRAY_SHOW: RestoreFromTray(g_hwnd); break; case ID_TRAY_HIDE: MinimizeToTray(g_hwnd); break; case ID_TRAY_PAUSE: TogglePause(); break; case ID_TRAY_CPU_DISPLAY: g_showCpuInTray = !g_showCpuInTray; UpdateTrayIcon(); break; case ID_EXPORT_CURRENT: ExportCurrentDataToCSV(); break; case ID_EXPORT_ALL_HISTORY: ExportAllHistoryToCSV(); break; case ID_TRAY_EXIT: RemoveTrayIcon(g_hwnd); DestroyWindow(g_hwnd); break; } } // Рисует кнопку void DrawButton(HDC hdc, int x, int y, int width, int height, const wchar_t* text, bool isHovered = false, bool isPressed = false, COLORREF color = DarkTheme::ButtonNormal) { COLORREF btnColor = color; if (isPressed) { // Затемняем цвет при нажатии int r = GetRValue(color) * 0.7; int g = GetGValue(color) * 0.7; int b = GetBValue(color) * 0.7; btnColor = RGB(r, g, b); } else if (isHovered) { // Осветляем цвет при наведении int r = std::min(GetRValue(color) + 20, 255); int g = std::min(GetGValue(color) + 20, 255); int b = std::min(GetBValue(color) + 20, 255); btnColor = RGB(r, g, b); } // Фон кнопки HBRUSH hBtnBrush = CreateSolidBrush(btnColor); HPEN hBorderPen = CreatePen(PS_SOLID, 1, DarkTheme::Border); HPEN hOldPen = (HPEN)SelectObject(hdc, hBorderPen); HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBtnBrush); // Рисуем кнопку с закругленными углами RoundRect(hdc, x, y, x + width, y + height, 8, 8); // Текст кнопки SetTextColor(hdc, DarkTheme::Text); SetBkMode(hdc, TRANSPARENT); RECT textRect = { x, y, x + width, y + height }; DrawTextW(hdc, text, -1, &textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // Восстанавливаем GDI объекты SelectObject(hdc, hOldPen); SelectObject(hdc, hOldBrush); DeleteObject(hBtnBrush); DeleteObject(hBorderPen); } // Отрисовка графика с поддержкой паузы void DrawChart(HDC hdc, int x, int y, int width, int height, const std::deque<double>& data, const wchar_t* title, COLORREF color, double maxValue = 100.0) { // Фон графика HBRUSH hChartBg = CreateSolidBrush(DarkTheme::Panel); RECT chartRect = { x, y, x + width, y + height }; FillRect(hdc, &chartRect, hChartBg); DeleteObject(hChartBg); // Заголовок графика SetTextColor(hdc, DarkTheme::Text); TextOutW(hdc, x, y - 20, title, wcslen(title)); // Индикатор паузы if (g_isPaused && !data.empty()) { SetTextColor(hdc, DarkTheme::PauseIndicator); TextOutW(hdc, x + width - 150, y - 20, L"⏸️ ПАУЗА", 8); } // Рамка графика HPEN hBorderPen = CreatePen(PS_SOLID, 1, DarkTheme::Border); HPEN hOldPen = (HPEN)SelectObject(hdc, hBorderPen); Rectangle(hdc, x, y, x + width, y + height); // Сетка HPEN hGridPen = CreatePen(PS_SOLID, 1, DarkTheme::Grid); SelectObject(hdc, hGridPen); // Горизонтальные линии (линии уровня) for (int i = 1; i < 5; i++) { int lineY = y + height * i / 5; MoveToEx(hdc, x, lineY, NULL); LineTo(hdc, x + width, lineY); // Подписи уровней wchar_t levelText[16]; swprintf(levelText, 16, L"%.0f%%", maxValue * (5 - i) / 5.0); SetTextColor(hdc, DarkTheme::TextSecondary); RECT textRect = { x - 30, lineY - 10, x, lineY + 10 }; DrawTextW(hdc, levelText, -1, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); } // Линия графика if (data.size() > 1) { // Более тонкая линия при паузе int lineWidth = g_isPaused ? 1 : 2; // Извлекаем компоненты цвета для затемнения при паузе int r = GetRValue(color); int g = GetGValue(color); int b = GetBValue(color); COLORREF lineColor; if (g_isPaused) { // Затемняем цвет на 30% при паузе lineColor = RGB(r * 0.7, g * 0.7, b * 0.7); } else { lineColor = color; } HPEN hLinePen = CreatePen(PS_SOLID, lineWidth, lineColor); SelectObject(hdc, hLinePen); int pointsCount = std::min((int)data.size(), width); int startX = x + width - pointsCount; // Находим первую точку int firstY = y + height - (int)((data.back() / maxValue) * height); MoveToEx(hdc, startX, firstY, NULL); // Рисуем линию for (int i = 1; i < pointsCount; i++) { int dataIndex = data.size() - i - 1; if (dataIndex < 0) break; int pointX = startX + i; int pointY = y + height - (int)((data[dataIndex] / maxValue) * height); LineTo(hdc, pointX, pointY); } // Точки на графике (только если не на паузе или мало точек) if (!g_isPaused || data.size() < 10) { HBRUSH hPointBrush = CreateSolidBrush(lineColor); HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hPointBrush); // Рисуем последние несколько точек int pointsToShow = std::min(5, pointsCount); for (int i = 0; i < pointsToShow; i++) { int dataIndex = data.size() - i - 1; if (dataIndex < 0) break; int pointX = startX + (pointsCount - i - 1); int pointY = y + height - (int)((data[dataIndex] / maxValue) * height); Ellipse(hdc, pointX - 2, pointY - 2, pointX + 2, pointY + 2); } SelectObject(hdc, hOldBrush); DeleteObject(hPointBrush); } DeleteObject(hLinePen); // Текущее значение wchar_t valueText[32]; swprintf(valueText, 32, L"%.1f%%", data.back()); SetTextColor(hdc, lineColor); TextOutW(hdc, x + width - 50, y - 20, valueText, wcslen(valueText)); } // Восстанавливаем перо SelectObject(hdc, hOldPen); DeleteObject(hBorderPen); DeleteObject(hGridPen); } // Рисует панель с закругленными углами void DrawRoundedPanel(HDC hdc, int x, int y, int width, int height) { HBRUSH hPanelBrush = CreateSolidBrush(DarkTheme::Panel); HPEN hBorderPen = CreatePen(PS_SOLID, 1, DarkTheme::Border); HPEN hOldPen = (HPEN)SelectObject(hdc, hBorderPen); HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hPanelBrush); // Рисуем прямоугольник с закругленными углами RoundRect(hdc, x, y, x + width, y + height, 10, 10); SelectObject(hdc, hOldPen); SelectObject(hdc, hOldBrush); DeleteObject(hPanelBrush); DeleteObject(hBorderPen); } // Отрисовка элементов управления void DrawControls(HDC hdc, RECT& clientRect) { if (!g_showControls) return; int controlY = clientRect.bottom - 120; int buttonWidth = 120; int buttonHeight = 30; int buttonSpacing = 10; // Панель управления DrawRoundedPanel(hdc, 20, controlY, clientRect.right - 40, 140); // Заголовок панели управления SetTextColor(hdc, DarkTheme::Text); HFONT hControlFont = CreateFont(16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); HFONT hOldFont = (HFONT)SelectObject(hdc, hControlFont); TextOutW(hdc, 40, controlY + 10, L"Управление графиками", 20); SelectObject(hdc, hOldFont); DeleteObject(hControlFont); // Кнопки (первый ряд) int buttonX = 40; int buttonY = controlY + 40; // Кнопка паузы/возобновления DrawButton(hdc, buttonX, buttonY, buttonWidth, buttonHeight, g_isPaused ? L"▶️ Возобновить" : L"⏸️ Пауза"); buttonX += buttonWidth + buttonSpacing; // Кнопка очистки DrawButton(hdc, buttonX, buttonY, buttonWidth, buttonHeight, L"🧹 Очистить"); buttonX += buttonWidth + buttonSpacing; // Кнопка скрытия/показа DrawButton(hdc, buttonX, buttonY, buttonWidth, buttonHeight, L"👁️ Скрыть управление"); // Второй ряд (кнопки экспорта) buttonX = 40; buttonY += buttonHeight + buttonSpacing + 5; // Кнопка экспорта текущих данных DrawButton(hdc, buttonX, buttonY, buttonWidth, buttonHeight, L"💾 Текущие в CSV", false, false, DarkTheme::ExportButton); buttonX += buttonWidth + buttonSpacing; // Кнопка экспорта всей истории DrawButton(hdc, buttonX, buttonY, buttonWidth, buttonHeight, L"📊 История в CSV", false, false, DarkTheme::ExportButton); } // Отрисовка всех метрик void DrawMetrics(HDC hdc, RECT& clientRect) { // Получаем текущие метрики double cpuUsage = GetCpuUsage(); DWORDLONG totalPhys, usedPhys, totalVirtual, usedVirtual; GetMemoryUsage(totalPhys, usedPhys, totalVirtual, usedVirtual); double memoryPercent = 100.0 * usedPhys / totalPhys; double virtualMemoryPercent = 0.0; if (totalVirtual > 0) { virtualMemoryPercent = 100.0 * usedVirtual / totalVirtual; } else { virtualMemoryPercent = 0.0; // Защита от деления на ноль } auto disks = GetDiskUsage(); // Обновляем историю (если не на паузе) UpdateHistory(cpuUsage, memoryPercent, virtualMemoryPercent, disks); // Настройки шрифтов HFONT hTitleFont = CreateFont(24, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); HFONT hNormalFont = CreateFont(18, 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); HFONT hSmallFont = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); HFONT hOldFont = (HFONT)SelectObject(hdc, hNormalFont); SetTextColor(hdc, DarkTheme::Text); SetBkMode(hdc, TRANSPARENT); int y = 20; int lineHeight = 35; int panelPadding = 15; // ===== ЗАГОЛОВОК ===== SelectObject(hdc, hTitleFont); RECT titleRect = clientRect; titleRect.top = y; titleRect.bottom = y + 40; wchar_t titleText[256]; swprintf(titleText, 256, L"📊 Системный монитор %s", g_isPaused ? L"(ПАУЗА)" : L""); DrawTextW(hdc, titleText, -1, &titleRect, DT_CENTER | DT_VCENTER); // Индикатор трея if (g_showCpuInTray) { SetTextColor(hdc, DarkTheme::TextSecondary); TextOutW(hdc, clientRect.right - 200, y + 10, L"📌 CPU в трее: ВКЛ", 17); } y += 60; // ===== ТЕКУЩИЕ ЗНАЧЕНИЯ (ЛЕВАЯ КОЛОНКА) ===== int leftColumn = 20; int columnWidth = (clientRect.right - 60) / 2; int panelWidth = columnWidth - 40; // Панель с текущими значениями DrawRoundedPanel(hdc, leftColumn, y, panelWidth, 250); int contentX = leftColumn + panelPadding; int contentY = y + panelPadding; SelectObject(hdc, hNormalFont); // CPU wchar_t cpuText[256]; swprintf(cpuText, 256, L"💻 ЦПУ: %.1f%%", cpuUsage); TextOutW(hdc, contentX, contentY, cpuText, wcslen(cpuText)); // Прогресс-бар CPU RECT cpuBar = { contentX + 219, contentY, contentX + 320, contentY + 20 }; // Фон прогресс-бара HBRUSH hCpuBgBrush = CreateSolidBrush(DarkTheme::ProgressBackground); FillRect(hdc, &cpuBar, hCpuBgBrush); DeleteObject(hCpuBgBrush); // Заполнение прогресс-бара int cpuWidth = static_cast<int>((cpuBar.right - cpuBar.left) * cpuUsage / 100.0); HBRUSH hCpuBrush = CreateSolidBrush(g_chartColors[0]); RECT cpuFill = { cpuBar.left, cpuBar.top, cpuBar.left + cpuWidth, cpuBar.bottom }; FillRect(hdc, &cpuFill, hCpuBrush); DeleteObject(hCpuBrush); // Рамка прогресс-бара HPEN hBarBorderPen = CreatePen(PS_SOLID, 1, DarkTheme::Border); HPEN hOldBarPen = (HPEN)SelectObject(hdc, hBarBorderPen); HBRUSH hOldBarBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); Rectangle(hdc, cpuBar.left, cpuBar.top, cpuBar.right, cpuBar.bottom); SelectObject(hdc, hOldBarPen); SelectObject(hdc, hOldBarBrush); DeleteObject(hBarBorderPen); contentY += lineHeight; // Оперативная память wchar_t ramText[256]; swprintf(ramText, 256, L"🧠 ОЗУ: %.1f%% (%s / %s)", memoryPercent, FormatBytes(usedPhys).c_str(), FormatBytes(totalPhys).c_str()); TextOutW(hdc, contentX, contentY, ramText, wcslen(ramText)); RECT ramBar = { contentX + 219, contentY, contentX + 320, contentY + 20 }; // Фон прогресс-бара HBRUSH hRamBgBrush = CreateSolidBrush(DarkTheme::ProgressBackground); FillRect(hdc, &ramBar, hRamBgBrush); DeleteObject(hRamBgBrush); // Заполнение прогресс-бара int ramWidth = static_cast<int>((ramBar.right - ramBar.left) * memoryPercent / 100.0); HBRUSH hRamBrush = CreateSolidBrush(g_chartColors[1]); RECT ramFill = { ramBar.left, ramBar.top, ramBar.left + ramWidth, ramBar.bottom }; FillRect(hdc, &ramFill, hRamBrush); DeleteObject(hRamBrush); // Рамка прогресс-бара hBarBorderPen = CreatePen(PS_SOLID, 1, DarkTheme::Border); hOldBarPen = (HPEN)SelectObject(hdc, hBarBorderPen); hOldBarBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); Rectangle(hdc, ramBar.left, ramBar.top, ramBar.right, ramBar.bottom); SelectObject(hdc, hOldBarPen); SelectObject(hdc, hOldBarBrush); DeleteObject(hBarBorderPen); contentY += lineHeight; // Виртуальная память wchar_t virtText[256]; swprintf(virtText, 256, L"📄 Файл подкачки: %.1f%% (%s / %s)", virtualMemoryPercent, FormatBytes(usedVirtual).c_str(), FormatBytes(totalVirtual).c_str()); TextOutW(hdc, contentX, contentY, virtText, wcslen(virtText)); RECT virtBar = { contentX + 219, contentY, contentX + 320, contentY + 20 }; // Фон прогресс-бара HBRUSH hVirtBgBrush = CreateSolidBrush(DarkTheme::ProgressBackground); FillRect(hdc, &virtBar, hVirtBgBrush); DeleteObject(hVirtBgBrush); // Заполнение прогресс-бара int virtWidth = static_cast<int>((virtBar.right - virtBar.left) * virtualMemoryPercent / 100.0); HBRUSH hVirtBrush = CreateSolidBrush(g_chartColors[2]); RECT virtFill = { virtBar.left, virtBar.top, virtBar.left + virtWidth, virtBar.bottom }; FillRect(hdc, &virtFill, hVirtBrush); DeleteObject(hVirtBrush); // Рамка прогресс-бара hBarBorderPen = CreatePen(PS_SOLID, 1, DarkTheme::Border); hOldBarPen = (HPEN)SelectObject(hdc, hBarBorderPen); hOldBarBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); Rectangle(hdc, virtBar.left, virtBar.top, virtBar.right, virtBar.bottom); SelectObject(hdc, hOldBarPen); SelectObject(hdc, hOldBarBrush); DeleteObject(hBarBorderPen); contentY += lineHeight; // Диски for (size_t i = 0; i < disks.size(); i++) { if (i >= 2) break; // Показываем только первые 2 диска в левой колонке wchar_t diskText[256]; swprintf(diskText, 256, L"💾 Диск %s: %.1f%%", disks[i].name.c_str(), disks[i].usage); TextOutW(hdc, contentX, contentY, diskText, wcslen(diskText)); RECT diskBar = { contentX + 219, contentY, contentX + 320, contentY + 20 }; // Фон прогресс-бара HBRUSH hDiskBgBrush = CreateSolidBrush(DarkTheme::ProgressBackground); FillRect(hdc, &diskBar, hDiskBgBrush); DeleteObject(hDiskBgBrush); // Заполнение прогресс-бара int diskWidth = static_cast<int>((diskBar.right - diskBar.left) * disks[i].usage / 100.0); HBRUSH hDiskBrush = CreateSolidBrush(g_chartColors[3 + i]); RECT diskFill = { diskBar.left, diskBar.top, diskBar.left + diskWidth, diskBar.bottom }; FillRect(hdc, &diskFill, hDiskBrush); DeleteObject(hDiskBrush); // Рамка прогресс-бара hBarBorderPen = CreatePen(PS_SOLID, 1, DarkTheme::Border); hOldBarPen = (HPEN)SelectObject(hdc, hBarBorderPen); hOldBarBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); Rectangle(hdc, diskBar.left, diskBar.top, diskBar.right, diskBar.bottom); SelectObject(hdc, hOldBarPen); SelectObject(hdc, hOldBarBrush); DeleteObject(hBarBorderPen); contentY += lineHeight; } // ===== ГРАФИКИ (ПРАВАЯ КОЛОНКА) ===== int chartX = columnWidth + 20; int chartY = 80; // Панель для графиков int graphPanelHeight = (CHART_HEIGHT + 40) * 3 - 20; DrawRoundedPanel(hdc, chartX, chartY, CHART_WIDTH + 20, graphPanelHeight); chartX += 10; chartY += 10; // График CPU DrawChart(hdc, chartX, chartY, CHART_WIDTH, CHART_HEIGHT, g_history.cpu, L"📈 История ЦПУ", g_chartColors[0]); chartY += CHART_HEIGHT + 30; // График памяти DrawChart(hdc, chartX, chartY, CHART_WIDTH, CHART_HEIGHT, g_history.memory, L"📊 История ОЗУ", g_chartColors[1]); chartY += CHART_HEIGHT + 30; // График дисков (первые 2) if (g_history.diskUsage.size() > 0) { wchar_t diskChartTitle[256]; swprintf(diskChartTitle, 256, L"💽 История диска %s", g_history.diskNames[0].c_str()); DrawChart(hdc, chartX, chartY, CHART_WIDTH, CHART_HEIGHT, g_history.diskUsage[0], diskChartTitle, g_chartColors[3]); } // ===== ЭЛЕМЕНТЫ УПРАВЛЕНИЯ ===== DrawControls(hdc, clientRect); // ===== СТАТУС И ИНФОРМАЦИЯ ===== // Время обновления SYSTEMTIME sysTime; GetLocalTime(&sysTime); wchar_t timeText[256]; swprintf(timeText, 256, L"🕐 %s: %02d:%02d:%02d", g_isPaused ? L"Paused" : L"Updated", sysTime.wHour, sysTime.wMinute, sysTime.wSecond); SetTextColor(hdc, g_isPaused ? DarkTheme::PauseIndicator : DarkTheme::TextSecondary); int infoY = clientRect.bottom - 30; if (g_showControls) { infoY -= 100; } TextOutW(hdc, clientRect.right - 250, infoY, timeText, wcslen(timeText)); // Статистика истории wchar_t statsText[256]; swprintf(statsText, 256, L"📋 История: %d точек", (int)g_history.cpu.size()); SetTextColor(hdc, DarkTheme::TextSecondary); TextOutW(hdc, 20, infoY, statsText, wcslen(statsText)); // Статус экспорта // SetTextColor(hdc, DarkTheme::ExportButton); // TextOutW(hdc, clientRect.right / 2 - 100, infoY, L"💾 Данные готовы к экспорту", 25); // Подсказки управления wchar_t helpText[256]; swprintf(helpText, 256, L"🚀 %s | %s | %s | %s | %s", g_isPaused ? L"Space - resume" : L"Space - pause", L"C - clear", L"H - hide controls", L"T - hide in tray", L"E - export CSV"); SetTextColor(hdc, RGB(120, 120, 120)); TextOutW(hdc, clientRect.right / 2 - 300, infoY + 25, helpText, wcslen(helpText)); // Восстанавливаем шрифты SelectObject(hdc, hOldFont); DeleteObject(hTitleFont); DeleteObject(hNormalFont); DeleteObject(hSmallFont); } // Обработка кликов по кнопкам void HandleButtonClick(int x, int y, RECT& clientRect) { if (!g_showControls) return; int controlY = clientRect.bottom - 120; int buttonWidth = 120; int buttonHeight = 30; int buttonSpacing = 10; // Первый ряд кнопок int buttonX = 40; int buttonY = controlY + 40; // Проверяем кнопку паузы RECT pauseBtnRect = { buttonX, buttonY, buttonX + buttonWidth, buttonY + buttonHeight }; if (x >= pauseBtnRect.left && x <= pauseBtnRect.right && y >= pauseBtnRect.top && y <= pauseBtnRect.bottom) { TogglePause(); return; } buttonX += buttonWidth + buttonSpacing; // Проверяем кнопку очистки RECT clearBtnRect = { buttonX, buttonY, buttonX + buttonWidth, buttonY + buttonHeight }; if (x >= clearBtnRect.left && x <= clearBtnRect.right && y >= clearBtnRect.top && y <= clearBtnRect.bottom) { ClearAllGraphs(); return; } buttonX += buttonWidth + buttonSpacing; // Проверяем кнопку скрытия/показа RECT hideBtnRect = { buttonX, buttonY, buttonX + buttonWidth, buttonY + buttonHeight }; if (x >= hideBtnRect.left && x <= hideBtnRect.right && y >= hideBtnRect.top && y <= hideBtnRect.bottom) { g_showControls = !g_showControls; InvalidateRect(g_hwnd, NULL, TRUE); return; } // Второй ряд кнопок (экспорт) buttonX = 40; buttonY += buttonHeight + buttonSpacing + 5; // Проверяем кнопку экспорта текущих данных RECT exportCurrentBtnRect = { buttonX, buttonY, buttonX + buttonWidth, buttonY + buttonHeight }; if (x >= exportCurrentBtnRect.left && x <= exportCurrentBtnRect.right && y >= exportCurrentBtnRect.top && y <= exportCurrentBtnRect.bottom) { ExportCurrentDataToCSV(); return; } buttonX += buttonWidth + buttonSpacing; // Проверяем кнопку экспорта всей истории RECT exportHistoryBtnRect = { buttonX, buttonY, buttonX + buttonWidth, buttonY + buttonHeight }; if (x >= exportHistoryBtnRect.left && x <= exportHistoryBtnRect.right && y >= exportHistoryBtnRect.top && y <= exportHistoryBtnRect.bottom) { ExportAllHistoryToCSV(); return; } } // Оконная процедура LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static bool isDragging = false; static POINT dragStart; switch (uMsg) { case WM_CREATE: { // Инициализация начальных значений для CPU FILETIME idleTime, kernelTime, userTime; GetSystemTimes(&idleTime, &kernelTime, &userTime); g_lastIdleTime.LowPart = idleTime.dwLowDateTime; g_lastIdleTime.HighPart = idleTime.dwHighDateTime; g_lastKernelTime.LowPart = kernelTime.dwLowDateTime; g_lastKernelTime.HighPart = kernelTime.dwHighDateTime; g_lastUserTime.LowPart = userTime.dwLowDateTime; g_lastUserTime.HighPart = userTime.dwHighDateTime; // Инициализация информации о системе GetSystemInfo(&g_sysInfo); g_memStatus.dwLength = sizeof(g_memStatus); // Устанавливаем тёмную тему для окна HMODULE hUxtheme = LoadLibrary(L"uxtheme.dll"); if (hUxtheme) { typedef HRESULT(WINAPI *SETWINDOWTHEME)(HWND, LPCWSTR, LPCWSTR); SETWINDOWTHEME SetWindowTheme = (SETWINDOWTHEME)GetProcAddress(hUxtheme, "SetWindowTheme"); if (SetWindowTheme) { SetWindowTheme(hwnd, L"DarkMode_Explorer", NULL); } FreeLibrary(hUxtheme); } // Создаем иконку в трее CreateTrayIcon(hwnd); // Запускаем таймер для обновления g_updateTimer = SetTimer(hwnd, ID_TIMER, UPDATE_INTERVAL, NULL); return 0; } case WM_TIMER: { // Принудительная перерисовка при обновлении таймера InvalidateRect(hwnd, NULL, TRUE); // Обновляем иконку в трее UpdateTrayIcon(); return 0; } case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); RECT clientRect; GetClientRect(hwnd, &clientRect); // Очищаем фон тёмным цветом HBRUSH hBackground = CreateSolidBrush(DarkTheme::Background); FillRect(hdc, &clientRect, hBackground); DeleteObject(hBackground); // Рисуем метрики и графики DrawMetrics(hdc, clientRect); EndPaint(hwnd, &ps); return 0; } case WM_LBUTTONDOWN: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); RECT clientRect; GetClientRect(hwnd, &clientRect); // Обрабатываем клики по кнопкам HandleButtonClick(x, y, clientRect); return 0; } case WM_SIZE: { // При изменении размера окна перерисовываем InvalidateRect(hwnd, NULL, TRUE); return 0; } case WM_SYSCOMMAND: { // Обработка сворачивания по кнопке в заголовке окна if (wParam == SC_MINIMIZE) { MinimizeToTray(hwnd); return 0; } break; } case WM_ERASEBKGND: { // Отключаем стандартную очистку фона для уменьшения мерцания return 1; } case WM_TRAYICON: { // Обработка событий иконки в трее switch (lParam) { case WM_RBUTTONUP: case WM_CONTEXTMENU: { POINT pt; GetCursorPos(&pt); ShowTrayContextMenu(hwnd, pt); break; } case WM_LBUTTONDBLCLK: // Двойной клик по иконке - восстановить окно if (g_isMinimizedToTray) { RestoreFromTray(hwnd); } break; case WM_LBUTTONUP: // Одинарный клик - показать меню POINT pt; GetCursorPos(&pt); ShowTrayContextMenu(hwnd, pt); break; } return 0; } case WM_COMMAND: { // Обработка выбора из меню трея if (lParam == 0) { HandleTrayMenuCommand(wParam); } return 0; } case WM_DESTROY: { if (g_updateTimer) { KillTimer(hwnd, g_updateTimer); } // Удаляем иконку из трея RemoveTrayIcon(hwnd); PostQuitMessage(0); return 0; } case WM_KEYDOWN: { switch (wParam) { case VK_ESCAPE: DestroyWindow(hwnd); break; case VK_SPACE: TogglePause(); break; case 'C': case 'c': case 'С': // Русская С case 'с': // Русская с ClearAllGraphs(); break; case 'H': case 'h': case 'Р': // Русская Р (H на английской раскладке) case 'р': // Русская р g_showControls = !g_showControls; InvalidateRect(hwnd, NULL, TRUE); break; case 'T': // case 't': case 'Е': // Русская Е (T на английской раскладке) case 'е': // Русская е if (g_isMinimizedToTray) { RestoreFromTray(hwnd); } else { MinimizeToTray(hwnd); } break; case 'E': case 'e': case 'У': // Русская У (E на английской раскладке) case 'у': // Русская у // Показываем диалог выбора типа экспорта { HMENU hMenu = CreatePopupMenu(); AppendMenuW(hMenu, MF_STRING, ID_EXPORT_CURRENT, L"Экспортировать текущие данные"); AppendMenuW(hMenu, MF_STRING, ID_EXPORT_ALL_HISTORY, L"Экспортировать всю историю"); POINT pt; GetCursorPos(&pt); SetForegroundWindow(hwnd); TrackPopupMenuEx(hMenu, TPM_RIGHTBUTTON | TPM_LEFTALIGN, pt.x, pt.y, hwnd, NULL); DestroyMenu(hMenu); } break; case VK_F5: // Принудительное обновление if (g_isPaused) { // Если на паузе, обновляем только текущие значения InvalidateRect(hwnd, NULL, TRUE); } break; } return 0; } case WM_MOUSEWHEEL: { // Прокрутка для масштабирования графиков short wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam); // Можно добавить логику масштабирования здесь InvalidateRect(hwnd, NULL, TRUE); return 0; } case WM_GETMINMAXINFO: { // Устанавливаем минимальный размер окна MINMAXINFO* mmi = (MINMAXINFO*)lParam; mmi->ptMinTrackSize.x = 800; mmi->ptMinTrackSize.y = 650; return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } // Точка входа int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { #ifdef TESTS using namespace std::chrono; auto start_ms = steady_clock::now(); // Если тест не пройдёт, assert выкинет ошибку в консоль TestFormatBytes(); std::cout << "TestFormatBytes passed." << std::endl; TestCpuCalculation(); std::cout << "TestCpuCalculation passed." << std::endl; TestMemoryCalculation(); std::cout << "TestMemoryCalculation passed." << std::endl; TestDiskDetection(); std::cout << "TestDiskDetection passed." << std::endl; TestHistoryBuffer(); std::cout << "TestHistoryBuffer passed." << std::endl; TestWStringToUTFString(); std::cout << "TestWStringToUTFString passed." << std::endl; auto end_ms = steady_clock::now(); auto ms = duration_cast<milliseconds>(end_ms - start_ms); std::cout << "All tests passed in " << ms.count() << " ms." << "\n"; #else WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = L"SystemMetricsWindow"; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; // Будем рисовать фон сами wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); // Создание окна g_hwnd = CreateWindowEx( 0, L"SystemMetricsWindow", L"System Monitor - CPU: 0%", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 900, 750, NULL, NULL, hInstance, NULL ); if (g_hwnd == NULL) return 0; ShowWindow(g_hwnd, nCmdShow); UpdateWindow(g_hwnd); // Цикл сообщений MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } #endif return 0; }