Мониторинг CPU и RAM на панели задач C++

от автора

Приветствую читателя этой статьи. Я студент, учусь по направлению «Приборостроение», но большую часть времени занимаюсь программированием. Все таки это меня привлекает больше. Задумывался по поводу смены ОС на Arch Linux, но пока отложил эту затею в долгий ящик. Смотрел различные ролики на YouTube и заметил, что многие пользователи ставят себе Polybar, в котором можно легко настраивать информацию, выводимую на нечто похожее на Панель задач в Windows. Тогда я подумал «А почему бы не сделать такое в винде?!» и сразу начал гуглить что к чему. Попытался найти готовые аналоги, но ничего не впечатлило, поэтому решил написать свою программу на C++.

Заранее хочу предупредить, это моя первая статья, поэтому было бы неплохо получить достаточно аргументированной критики. Может я что-то пропустил или недостаточно понятно объяснил, жду фидбека.

Выглядеть оно будет примерно так:

Рис.1 - Голубая иконка - RAM. Зеленая - загрузка CPU

Рис.1 — Голубая иконка — RAM. Зеленая — загрузка CPU

При наведении на иконку ОЗУ мы увидим загрузку памяти в GB и %, а на иконку CPU — загрузку процессора в процентах. Цвет иконку cpu будет меняться в зависимости от загрузки:

  • Зеленый — загрузка <25%

  • Оранжевый — загрузка 25-50%

  • Красный — >50%

Приступаем к разработке

Для начала создадим оконное приложение в VS и удалим все лишнее оттуда из .cpp и .rc файлов.

Подготовим иконки и добавим их в папку проекта.

рис. 2

рис. 2
рис. 3

рис. 3

Эту иконку я нашел на сайте -> https://www.flaticon.com/ru/free-icons/prosessor. Там же можно найти и иконку RAM. Они там в черном цвете, поэтому можно в обычной Paint залить их в нужный цвет, а через онлайн ресурсы конвертировать в .ico. Иконки можно подобрать любые, которые вам нравятся

В файле .rc (ресурсов) добавляем иконки следующим образом, чтобы их не пришлось тянуть рядом с нашим готовым exeшником.

IDR_CPUICON1 ICON "cpu4.ico"
IDR_CPUICON2 ICON "cpu6.ico"
IDR_CPUICON3 ICON "cpu5.ico"
IDR_RAMICON0 ICON "ram0.ico"

А в файле Recource.h добавляем им idшники
#define IDR_CPUICON1131
#define IDR_CPUICON2132
#define IDR_CPUICON3133
#define IDR_RAMICON0134

Создадим 2 структуры, которые как раз таки и отвечают за иконку программы в таск баре:

NOTIFYICONDATA cpu_status_icon;
NOTIFYICONDATA ram_status_icon;

Подробнее о структуре -> https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa

И 2 хендлера таймеров, которые будут работать в параллельных потоках, таким образом вывод информации будет независимый:

HANDLE htimer_ram = NULL;
HANDLE htimer_cpu = NULL;

void CALLBACK TimerRAM(PVOID pVoid, BOOLEAN TimerOrWaitFired) {     static wchar_t ram_info[128];     if (GlobalMemoryStatusEx(&mem_info)) {         static uint16_t totalPhys = (uint16_t)ceil((float)mem_info.ullTotalPhys / 1024 / 1024 / 1024);         float physUsed = (float)(mem_info.ullTotalPhys - mem_info.ullAvailPhys) / 1024 / 1024 / 1024;         uint16_t usagePercent = mem_info.dwMemoryLoad;          swprintf(ram_info, 128, L"%.2f\\%u Gb   %u%%", physUsed, totalPhys, usagePercent);         lstrcpy(ram_status_icon.szTip, ram_info);         Shell_NotifyIcon(NIM_MODIFY, &ram_status_icon);     }  }  void CALLBACK TimerCPU(PVOID pVoid, BOOLEAN TimerOrWaitFired) {     static wchar_t cpu_inf[8];     double cpu_total = GetCPULoad();     if (cpu_total < 25)         cpu_status_icon.hIcon = cpu_icons[0];     else if (cpu_total < 50)         cpu_status_icon.hIcon = cpu_icons[1];     else if (cpu_total >= 50)         cpu_status_icon.hIcon = cpu_icons[2];     swprintf(cpu_inf, 8, L"%.2f %%", cpu_total);     lstrcpy(cpu_status_icon.szTip, cpu_inf);     Shell_NotifyIcon(NIM_MODIFY, &cpu_status_icon); }

Обновлять информацию об ОЗУ будет сразу в таймере. Функция загрузки проца выглядит следующим образом:

float GetCPULoad() {     static FILETIME idleTimePrev = {}, kernelTimePrev = {}, userTimePrev = {};      FILETIME idleTime, kernelTime, userTime;     if (!GetSystemTimes(&idleTime, &kernelTime, &userTime)) return 0.0;      auto toUInt64 = [](FILETIME ft) {         return ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime;         };      uint64_t idleDiff = toUInt64(idleTime) - toUInt64(idleTimePrev);     uint64_t kernelDiff = toUInt64(kernelTime) - toUInt64(kernelTimePrev);     uint64_t userDiff = toUInt64(userTime) - toUInt64(userTimePrev);      idleTimePrev = idleTime;     kernelTimePrev = kernelTime;     userTimePrev = userTime;      uint64_t total = kernelDiff + userDiff;     return total ? (1.0 - (float)idleDiff / total) * 100.0 : 0.0; }

Затем инициализируем все необходимые переменные, таймеры и т.п.:

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {    hInst = hInstance; // Сохранить маркер экземпляра в глобальной переменной     HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);     if (!hWnd)    {       return FALSE;    }    cpu_icons[0] = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDR_CPUICON1), IMAGE_ICON, 64, 64, 0);    cpu_icons[1] = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDR_CPUICON2), IMAGE_ICON, 64, 64, 0);    cpu_icons[2] = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDR_CPUICON3), IMAGE_ICON, 64, 64, 0);    cpu_status_icon.cbSize = sizeof(NOTIFYICONDATA);    cpu_status_icon.hWnd = hWnd;    cpu_status_icon.uID = 1;    cpu_status_icon.hIcon = cpu_icons[0];    lstrcpy(cpu_status_icon.szTip, L"%");    cpu_status_icon.uFlags = NIF_ICON | NIF_TIP;    Shell_NotifyIcon(NIM_ADD, &cpu_status_icon);     mem_info.dwLength = sizeof(mem_info);     ram_status_icon.cbSize = sizeof(NOTIFYICONDATA);    ram_status_icon.hWnd = hWnd;    ram_status_icon.uID = 2;    ram_status_icon.hIcon = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDR_RAMICON0), IMAGE_ICON, 64, 64, 0);;    lstrcpy(ram_status_icon.szTip, L"%");    ram_status_icon.uFlags = NIF_ICON | NIF_TIP;    Shell_NotifyIcon(NIM_ADD, &ram_status_icon);      if (!CreateTimerQueueTimer(&htimer_cpu, NULL, TimerCPU, NULL, 1000, 1500, WT_EXECUTEDEFAULT))    {        std::cerr << "Error cpu timer\n";        return -1;    }     if (!CreateTimerQueueTimer(&htimer_ram, NULL, TimerRAM, NULL, 1000, 1500, WT_EXECUTEDEFAULT))    {        std::cerr << "Error ram timer\n";        return -2;    }    return TRUE; }

Не забываем удалить таймеры при завершении работы:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {     switch (message)     {     case WM_DESTROY:         DeleteTimerQueueTimer(NULL, htimer_cpu, NULL);         DeleteTimerQueueTimer(NULL, htimer_ram, NULL);         PostQuitMessage(0);         break;     default:         return DefWindowProc(hWnd, message, wParam, lParam);     }     return 0; }

Готовый проект можно найти на моем гитхабе.


ссылка на оригинал статьи https://habr.com/ru/articles/928602/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *