Приветствую читателя этой статьи. Я студент, учусь по направлению "Приборостроение", но большую часть времени занимаюсь программированием. Все таки это меня привлекает больше. Задумывался по поводу смены ОС на 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;
}

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

Комментарии (14)


  1. Rax12
    17.07.2025 16:13

    Вот бы тоже самое но вывод загрузки в процентах. Как раз искал себе такое и ничего толкового не нашел....


    1. Penguin_pelmen Автор
      17.07.2025 16:13

      Вы про загрузку ОЗУ в процентах? Если да, так она есть. Там такой формат: занято/всего Gb ...%

      Вы наводите мышку на иконку и видите нагрузку на озу и проц


    1. egribanov
      17.07.2025 16:13

      Можно поменять иконки на колличество процентов например с шагом 25-30%


      1. Penguin_pelmen Автор
        17.07.2025 16:13

        Можно, Вы абсолютно правы. Когда писал эту программу, решил, что будет удобнее сделать иначе. Изменение цвета, как по мне, воспринимать. А при наведении курсора на иконку вы увидите над ней более подробную информацию. К тому же потребовалось бы больше иконок


        1. aX1v
          17.07.2025 16:13

          Не нужно больше иконок - надо создавать иконки на лету, рисуя на них значение процентов. Неоптимизированный пример кода для создания иконки из текста:

          HICON CreateTextIcon( HWND hWnd, int size, TCHAR *szText)
          {
              HDC hdc = GetDC( hWnd );
              HDC hdcMem = CreateCompatibleDC( hdc );
              HBITMAP hBitmap = CreateCompatibleBitmap( hdc, size, size );
              HBITMAP hBitmapMask = CreateCompatibleBitmap( hdc, size, size );
              ReleaseDC( hWnd, hdc );
          
              SelectObject( hdcMem, hBitmap );
          
              HBRUSH brush = CreateSolidBrush( RGB(0,0,0) );
              SelectObject( hdcMem, brush );
              PatBlt( hdcMem, 0, 0, size, size, PATCOPY );
          
              // Draw percentage
              int font_size = lstrlen(szText) < 3 ? size : (3*size)/4;
              HFONT hFont = CreateFont( font_size, 0, 0, 0, FW_BOLD, 0, 0, 0, ANSI_CHARSET, 0, 0, 0, 0, TEXT ("Arial") );
              HFONT hFont_orig = (HFONT) SelectObject( hdcMem, hFont );
              SetBkColor( hdcMem, RGB(0,0,0) );
              SetTextColor( hdcMem, RGB(255,255,255) );
              RECT rect;
              SetRect( &rect, 0, 0, size, size );
              DrawText( hdcMem, szText, lstrlen (szText), &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
          
              ICONINFO iconInfo;
              iconInfo.fIcon = TRUE;
              iconInfo.xHotspot = 0;
              iconInfo.yHotspot = 0;
              iconInfo.hbmMask = hBitmapMask;
              iconInfo.hbmColor = hBitmap;
              HICON hIcon = CreateIconIndirect( &iconInfo );
          
              SelectObject( hdcMem, hFont_orig );
              DeleteObject( hFont );
              DeleteObject( brush );
              DeleteObject( hBitmapMask );
              DeleteObject( hBitmap );
              DeleteDC( hdcMem );    
          
              return hIcon;
          }


    1. fulvert
      17.07.2025 16:13

      Гляньте HW monitor


  1. Gordon01
    17.07.2025 16:13

    Здесь нет С++, везде чистый Си


    1. anasana
      17.07.2025 16:13

      В смысле чистый Winapi, а не MFC?


    1. rbdr
      17.07.2025 16:13

      Есть там чутка лямбд от модерна


  1. fulvert
    17.07.2025 16:13

    Наведение на иконку-изначальное зло. Лично я считаю, что надо выводить цифры соответствующими цветами. А вообще подобное насколько помню реализовано в HW monitor.


    1. Penguin_pelmen Автор
      17.07.2025 16:13

      На счет HW monitor не знал, но вы правы, спасибо


  1. leni8ec
    17.07.2025 16:13

    Идея супер!

    В Run-cat - как раз не хватает загрузки оперативной памяти.

    А так идея с двумя иконками - вполне удобно. Только что бы без наведения можно было узнавать статус (не важно - в виде процентов или какой-либо анимации, но с цветами - все же тяжелее будет восприятие)


  1. gaussssss
    17.07.2025 16:13

    Могу ошибаться, но вроде все это уже есть в том же core temp. Ну и да, лучше мониторинг без наведения мыши, лишние действия.


  1. Andezion
    17.07.2025 16:13

    Прикольно получилось!!