Search
Write a publication
Pull to refresh

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

Level of difficultyEasy
Reading time4 min
Views5.1K

Приветствую читателя этой статьи. Я студент, учусь по направлению "Приборостроение", но большую часть времени занимаюсь программированием. Все таки это меня привлекает больше. Задумывался по поводу смены ОС на 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_CPUICON1 131
#define IDR_CPUICON2 132
#define IDR_CPUICON3 133
#define IDR_RAMICON0 134

Создадим 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;
}

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

Tags:
Hubs:
+8
Comments14

Articles