Мониторинг CPU + RAM в трее. Часть 2

от автора

Приветствую, уважаемый читатель. Ранее, я уже писал подобную статью (ссылка на нее есть ниже). Рекомендую прочесть ее прежде, чем переходить к этой, так как из первой части фундаментальные вещи остались не тронутыми.

ПЕРВАЯ ЧАСТЬ

Проанализировав статистику по предыдущей статье я понял, что этот проект может быть действительно актуален пользователям Windows. Решил последовать советам из комментариев и это того стоило.

Напомню главную задачу: фоновая программа, которая выводит в трей информацию о загрузки CPU и ОЗУ.

Как изменения были введены:

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

  • Градиентный переход от зеленого к красного с помощью линейной интерполяции для иконки загрузки CPU

  • Окно настройки (отключение вывода информации о процессоре или памяти, + возможность запускать окно настройки при запуске программы).

Каждый пункт постараюсь описать максимально качественно.

Для тех, кто использовал предыдущее решение.

В прошлый раз мы использовали готовые иконки. Так вот их можно смело удалять. Также нужно удалить их из .rc файла

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

И из Recourse.h

#define IDR_CPUICON1131  #define IDR_CPUICON2132  #define IDR_CPUICON3133  #define IDR_RAMICON0134

Так как готовые иконки больше не используются, то наш список иконок больше не нужен. В методе инициализации убираем следующие строки

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);

Общая подготовка

Для генерации иконок с числовыми значениями я использовал библиотеку GDI. Сначала выбор пал на нее, так как позволяет быстро рисовать изображения даже на старом железе в связи с чем нормально воспринимается частое обновление. Но возникла очень неприятная проблема — она не поддерживает альфа канал, только RGB. Из-за чего была иконка на фоне черного квадрата, что выглядело не очень красиво. Поэтому было принято решение использоваться более новую версию GDI+. Дабы не генерировать иконку постоянно после каждого тика, то создадим 2 хэш-таблицы. Если значения будут повторяться, то иконку будем тянуть из нее, таким образом избавимся от лишней перерисовки.

Для начала подключим библиотеку в наш проект:

#include <objidl.h> #include <gdiplus.h>  #pragma comment(lib, "gdiplus.lib")

Заголовочный файл objidl.h не содержит инструментов для работы с графикой, но может быть необходимым для работы с GDI+. Все дело в том, что для инициализации GDI+ нужны функции, которые не входят в саму библиотеку.

Теперь добавим несколько «констант» в препроцессор

#define ICON_SIZE 64 #define ICON_CLICK (WM_APP + 1) #define CPU_CLICK (WM_APP + 2) #define RAM_CLICK (WM_APP + 3) #define OPEN_AT_START (WM_APP + 4) #define SETTINGS_MONITOR_NAME L"SETTINGS_MONITOR" #define RAM_COLOR Gdiplus::Color(255, 200, 90, 240)

ICON_SIZE — размер иконки
ICON_CLICK — будем вызывать окно настроек по нажатию на одну из иконок
CPU_CLICK — для обработки выбора в checkbox CPU
RAM_CLICK — аналогично с ОЗУ
OPEN_AT_START — для обработки клика на checkbox, который отвечает за настройку «Открыть при запуске» окно настроек. Ниже объясню, зачем вообще эта настройка нужна
SETTINGS_MONITOR_NAME — имя переменной среды, которая будет хранить все настройки нашей программы.
RAM_COLOR — цвет иконки для ОЗУ. Я выбрал фиолетовый цвет. Можете подобрать понравившийся вам цвет и вписать его ARGB значение в это поле

Теперь напишем отдельный класс для checkbox и вынесем его в заголовочный файл checkbox.h, дабы не загромождать код. Делается это для более удобной работы с самим объектом, поскольку создается он через CreateWindowEx. Класс максимально простой:

checkbox.h
#include <windows.h> #include <iostream> #include <string>  class Checkbox { private: HWND parentWindow; HMENU hMenu; bool checked; HWND hwnd; public:  Checkbox(HWND hwnd_, HINSTANCE hInstance, UINT id_, LPCWCHAR text_, UINT x, UINT y, UINT width, UINT height)  : parentWindow(hwnd_), hMenu((HMENU)id_) { hwnd = CreateWindowEx(NULL,  L"BUTTON",  text_,  WS_VISIBLE | WS_CHILD | BS_CHECKBOX,  x,  y,  width,  height,  parentWindow,  hMenu,  hInstance,  nullptr); UnCheck(); }  void Check() { checked = true; SendMessage(hwnd, BM_SETCHECK, checked, 0); }  void UnCheck() { checked = false; SendMessage(hwnd, BM_SETCHECK, checked, 0); }  void Block() { EnableWindow(hwnd, FALSE); }  void UnBlock() { EnableWindow(hwnd, TRUE); }  bool Status() { return checked; }  void Revers() { checked = !checked; SendMessage(hwnd, BM_SETCHECK, checked, 0); } };

Не забудьте подключить хедер:

#include "checkbox.h"

Теперь добавим несколько новых переменных в проект:

Глобальные переменные
HINSTANCE hInst; WCHAR szTitle[MAX_LOADSTRING]; WCHAR szWindowClass[MAX_LOADSTRING]; HANDLE htimer_ram = NULL; HANDLE htimer_cpu = NULL; NOTIFYICONDATA cpu_status_icon; NOTIFYICONDATA ram_status_icon; MEMORYSTATUSEX mem_info; //новые ULONG_PTR g_gdiplusToken = 0; //токен для использования GDI+ std::map<uint16_t, HICON> hash_cpu_icons; //хэш-таблица для хранения иконок CPU std::map<uint16_t, HICON> hash_ram_icons; //аналогично для ОЗУ  byte show_window_flag; //флаг состояния окна 0x0 - скрыто; 0x5 - открыто byte settings_param = 0b0111; //переменная настроек всей программы //Последние 3 бита отвечают за все показ иконок и запуск окна при запуске byte cpu_show = 0x2; // состояние иконки cpu. 0x2 - показано, 0x0 -  скрыто byte ram_show = 0x2; // аналогично для озу  //указатели на 3 чекбокса настроек Checkbox *cpu_checkbox; Checkbox *ram_checkbox; Checkbox *open_start_checkbox;

Генерация иконок

Реализуем пару функция для инициализации и очистки ресурсов, необходимых для библиотеки GDI+.

Работа с GDI+
void InitGDIPlus() {     Gdiplus::GdiplusStartupInput gdiplusStartupInput;     Gdiplus::GdiplusStartup(&g_gdiplusToken, &gdiplusStartupInput, nullptr); }  void ShutdownGDIPlus() {     if (g_gdiplusToken != 0)         Gdiplus::GdiplusShutdown(g_gdiplusToken); }

А теперь реализуем функцию для генерации иконки:

CreateIconWithNumber
ICON CreateIconWithNumber(int number, int size, int fontSize, Gdiplus::Color textColor) {     std::wstring text = std::to_wstring(number);      //создаем битмапу с константой PixelFormat32bppARGB - 32 бита на пиксель:     //по 8 бит на каждые 4 канала ARGB     Gdiplus::Bitmap bitmap(size, size, PixelFormat32bppARGB);      //объявляем класс, которые предоставляет инструменты для рисования на устройстве вывода     Gdiplus::Graphics graphics(&bitmap);     graphics.Clear(Gdiplus::Color(0, 0, 0, 0)); // Прозрачный фон      //инициализируем шрифт     Gdiplus::FontFamily fontFamily(L"Segoe UI");     Gdiplus::Font font(&fontFamily, static_cast<Gdiplus::REAL>(fontSize), Gdiplus::FontStyleBold, Gdiplus::UnitPixel);     Gdiplus::SolidBrush brush(textColor);      // Для вертикального центрирования измеряем высоту одной цифры     Gdiplus::RectF charRect;     graphics.MeasureString(L"0", 1, &font, Gdiplus::PointF(0, 0), &charRect);     float y = (size - charRect.Height) / 2.0f;      float x = 0.0f;     float spacingFactor = 0.65f; // Коэффициент уменьшения межбуквенного интервала (больше => плотнее)      for (wchar_t ch : text)     {         wchar_t str[2] = { ch, 0 };          // Измеряем ширину символа         graphics.MeasureString(str, 1, &font, Gdiplus::PointF(0, 0), &charRect);          // Рисуем символ по координатам (x, y), вертикально выровнено по центру         graphics.DrawString(str, 1, &font, Gdiplus::PointF(x, y), &brush);          // Смещаем x с учётом коэффициента уменьшения межсимвольного интервала         x += charRect.Width * spacingFactor;     }      HICON hIcon = nullptr;     bitmap.GetHICON(&hIcon);     return hIcon; }

Решил ввести коэффициент уменьшения межбуквенного интервала, так как из-за стандартного интервала часть второй цифр обрезалась. А так можно регулировать и размер шрифта и межсимвольный интервал. По-моему, довольно гибкая настройка

В аргументах она принимает:

  • Число, которое будет выведено на иконке

  • размер самой иконки (в нашем случае 64)

  • размер шрифта

  • Цвет числа

Теперь немного изменим наши функции обратного вызова для таймером.

Таймер для ОЗУ
void CALLBACK TimerRAM(PVOID pVoid, BOOLEAN TimerOrWaitFired) {     static wchar_t ram_info[64];     ZeroMemory(ram_info, 64);     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;         HICON icon;         if (hash_ram_icons.find((uint16_t)usagePercent) == hash_ram_icons.end())         {             icon = CreateIconWithNumber(usagePercent, ICON_SIZE, 50, RAM_COLOR);             hash_ram_icons.insert({ (uint16_t)usagePercent, icon});         }         ram_status_icon.hIcon = hash_ram_icons[(uint16_t)usagePercent];         swprintf(ram_info, 64, L"%.2f\\%u Gb   %u%%", physUsed, totalPhys, usagePercent);         lstrcpy(ram_status_icon.szTip, ram_info);         Shell_NotifyIcon(NIM_MODIFY, &ram_status_icon);     } }
Таймер для CPU
void CALLBACK TimerCPU(PVOID pVoid, BOOLEAN TimerOrWaitFired) {     static wchar_t cpu_inf[8];     ZeroMemory(cpu_inf, 8);     double cpu_total = GetCPULoad();     HICON icon;     if (hash_cpu_icons.find((uint16_t)cpu_total) == hash_cpu_icons.end())     {         icon = CreateIconWithNumber((uint16_t)cpu_total, ICON_SIZE, 50, GetGradColor(cpu_total));         hash_cpu_icons.insert({ (uint16_t)cpu_total,  icon });     }     cpu_status_icon.hIcon = hash_cpu_icons[(uint16_t)cpu_total];     swprintf(cpu_inf, 8, L"%.2f %%", cpu_total);     lstrcpy(cpu_status_icon.szTip, cpu_inf);     Shell_NotifyIcon(NIM_MODIFY, &cpu_status_icon); }

Ранее оно выглядело следующим образом:

Было
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); }

Логика такая: «Есть ли иконка в нашей хэш таблице с числом n?»
true — Берем эту иконку, false — рисуем ее и добавляем в хэш-таблицу. Таким образом избавимся от постоянной перерисовки.

Можно заметить, что теперь в таймере для процессора цвет меняется не ступенями. Применяется неизвестная функция GetGradColor(); Давайте в этом разбираться

Линейная интерполяция для градиента

Чтобы плавно менять значения в зависимости от загрузки я использовал линейную интерполяцию. Суть проста:

LERP(A, B, t) = A + t * (B - A) A < B, t ∈ [0; 1]

Это простое выражение позволяет получить промежуточное значение между числом A и B, соответствующее параметру t. Рассмотрим пример

Какое число будет соответствовать параметру t = 0.5 в промежутке [0, 50]?

LERP(0, 50, 0.5) = 0 + 0.5 * (50 - 0) = 25

Этот принцип и будем использоваться. Но сначала определимся с нашими промежутками. Как будет меняться наш цвет? Выделим 3 точки

  • Зеленый — RGB(0, 255, 0) — соответствует 0% загрузки CPU

  • Оранжевый — RGB(255, 165, 0) — соответствует 50% загрузки CPU

  • Красный RGB(255, 0, 0) — соответствует 100% загрузки CPU

Теперь введем параметр ] u = x / 50, где x — загрузка CPU в процентах

Этот параметр даст нам 2 ветки для интерполяции:
u < 1 и u > 1

Теперь интерполируем:

  • Канал B = const = 0

  • Канал R = 255 * min(u, 1) — здесь A = 0, B = 255. Если наше u > 1, соответственно загрузка CPU > 50%, а это значит, канал R больше не меняется. С помощью функций max и min будем это регулировать

  • Канал G = 255 - 90 * min(u, 1) - 165  * max(u-1, 0)

С математикой разобрались, теперь реализуем функцию:

Функция GetGradColor
Gdiplus::Color GetGradColor(double x) {     double u = x / 50.0;     Gdiplus::Color c(         255, //alfa         int(255 * min(u, 1.0)), //r         int(255 - 90 * min(u, 1.0) - 165 * max(u - 1.0, 0)), //g         0);  //b     return c; }

Попробуйте проверить это на бумаге. Поэтапно.
Часть 255 - 90 * u будет уменьшать G канал до 165 при u < 1,

а затем до 0 при u > 1, 165 - 165 * (u - 1)

Инициализация параметров и работа с настройками

Для начала расскажу идею для настройки программы. У нас есть переменная settings_param. последние 3 бита — есть наши настройки.

  • Включен последний бит — отображается статус CPU

  • Включен предпоследний бит — отображается статус RAM

  • Включен 3-й бит с конца — окно настроек открывается при запуске программы

Чтобы эти настройки сохранялись при перезапуске ОС, будем создавать переменную среды, об этом в следующей главе.

Представлю код стандартной функции инициализации с комментариями.

InitInstance
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {    hInst = hInstance;     HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,       300, 300, 300, 300, nullptr, nullptr, hInstance, nullptr);     if (!hWnd)    {       return FALSE;    } //этот блок разберем в следующей главе    try    {         settings_param = ReadSettings();    }    catch (std::runtime_error &msg)    {        settings_param = 0b0111;        WriteSettings();    }    //инициализируем чекбоксы, задаем им координаты на нашем окне и текст    cpu_checkbox = new Checkbox(hWnd, hInstance, CPU_CLICK, L"CPU", 100, 50, 100, 30);    ram_checkbox = new Checkbox(hWnd, hInstance, RAM_CLICK, L"RAM", 100, 100, 100, 30);    open_start_checkbox = new Checkbox(hWnd, hInstance, OPEN_AT_START, L"Open at start", 100, 150, 110, 30); //инициализируем GDI+    InitGDIPlus();       mem_info.dwLength = sizeof(mem_info);    //Все как в первой части. Но теперь мы заранее не выставляем готовые иконки,   //ведь нам нужно было отказаться от них    //в поле uCallbackMessage указываем наше значение, которое будем обрабатывать   //при нажатии на иконку    cpu_status_icon.cbSize = sizeof(NOTIFYICONDATA);    cpu_status_icon.hWnd = hWnd;    cpu_status_icon.uID = 1;    cpu_status_icon.uCallbackMessage = ICON_CLICK;    cpu_status_icon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;     ram_status_icon.cbSize = sizeof(NOTIFYICONDATA);    ram_status_icon.hWnd = hWnd;    ram_status_icon.uID = 2;    ram_status_icon.uCallbackMessage = ICON_CLICK;    ram_status_icon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;  //проверяем настройки каждого бита и выставляем необходимые настройки для //чекбокса и флагов    if (settings_param & 0b0100)    {        show_window_flag = 5;        open_start_checkbox->Check();    }    if (settings_param & 0b0010)    {        ram_show = 0;        ram_checkbox->Check();    }    if (settings_param & 0b0001)    {        cpu_show = 0;        cpu_checkbox->Check();    } //ну а теперь в соответсвии с флагами у нас будут отображаеться необходимые элементы    ShowWindow(hWnd, show_window_flag);    Shell_NotifyIcon(cpu_show, &cpu_status_icon);    Shell_NotifyIcon(ram_show, &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; }

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

CALLBACK WndProc
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {     switch (message)     {     case ICON_CLICK:         if (lParam == WM_LBUTTONDOWN)         {             show_window_flag ^= 5;             ShowWindow(hWnd, show_window_flag);         }         break;     case WM_CLOSE:         show_window_flag = 0;         ShowWindow(hWnd, show_window_flag);         break;     case WM_COMMAND:         if (HIWORD(wParam) == BN_CLICKED)         {             switch (LOWORD(wParam))             {                 case CPU_CLICK:                     cpu_checkbox->Revers();                     cpu_show ^= 0x2;                     settings_param ^= 0b001;                     Shell_NotifyIcon(cpu_show, &cpu_status_icon);                     open_start_checkbox->UnBlock();                     WriteSettings();                     break;                 case RAM_CLICK:                     ram_checkbox->Revers();                     ram_show ^= 0x2;                     settings_param ^= 0b010;                     Shell_NotifyIcon(ram_show, &ram_status_icon);                     open_start_checkbox->UnBlock();                     WriteSettings();                     break;                 case OPEN_AT_START:                     open_start_checkbox->Revers();                     settings_param ^= 0b100;                     WriteSettings();                     break;                 }                 if (!(settings_param & 1) && !(settings_param & 2))                 {                     settings_param = 0b100;                     open_start_checkbox->Check();                     open_start_checkbox->Block();                     WriteSettings();                 }         }         break;     case WM_DESTROY:         ShutdownGDIPlus();         Shell_NotifyIcon(NIM_DELETE, &cpu_status_icon);         Shell_NotifyIcon(NIM_DELETE, &ram_status_icon);         DeleteTimerQueueTimer(NULL, htimer_cpu, NULL);         DeleteTimerQueueTimer(NULL, htimer_ram, NULL);         PostQuitMessage(0);         break;     default:         return DefWindowProc(hWnd, message, wParam, lParam);     }     return 0; }

Пройдемся подробнее по блокам снизу-вверх.

Блок WM_DESTROY:

Все просто. При завершении работы программы очищаем ресурсы GDI+, удаляем иконки и удаляем таймеры.

Блок WM_COMMAND:

wParam содержит id чекбокса, по которому был сделан клик. Что происходит для CPU_CLICK и RAM_CLICK (по сути это и id наших таймеров):

  • Меняем значение checkbox на противоположное

  • Устанавливаем флаг отображения значка операцией XOR

  • Инвертируем бит в переменной нашей глобальной настройки settings_param

  • Обновляем структуру иконки

  • Разблокируем чекбокс open_start_checkbox

  • Записываем информацию о настройках в переменную среду

Я столкнулся со следующей важной проблемой: если юзер отключил показ обоих иконок и отключил возможность открытия окна настроек при запуске программы, то у просто повиснет фоновый процесс, а взаимодействие с ним будет невозможным. Придется через диспетчер задач отключать процесс, лезть в реестр, менять значение переменной и заново запускать программу. Поэтому решение следующее: если отключен показ обоих иконок, то окно настроек отключить будет невозможно. Жестко включим чекбокс, выставим нужные настройки и зафиксируем эти настройки в settings_param и отключим чекбокс. Этим и занимается блок с if-ом.

Блок WM_CLOSE:

Вход в него осуществляется при попытке закрыть окно нажатием на крестик. Не обрабатывая это сообщение, наше окно просто закроется. Действие довольно машинальное и завершать работу таким образом будет не совсем удобно (это банально может быть случайный клик). Поэтому при нажатии на крестик мы просто скроем окно настроек.

Блок ICON_CLICK:

Как раз обработка нажатия на иконку. С помощью XOR меняем флаг и скрываем/показываем окно настроек.

Таким образом мы имеем довольно удобную работу с окном настроек и их применением, так как предусмотрены интуитивные действия пользователя.

Сохранение настроек в переменную среды

Нужно реализовать 2 функции — чтение и запись переменной среды. Будем использоваться переменную в разделе пользователя.

Чтобы использовать переменную для машины, то могут потребоваться права администратора. Да и оно не особо нужно нам. Можете посмотреть переменные, которые используются в вашим пользователем прямо сейчас. Нажмите WIN+R, введите в окно regedit (реестр Windows) и перейдите во вкладку \HKEY_CURRENT_USER\Environment. Сюда мы и будем записывать нашу переменную. При перезапуске системы она сохранит свое значение, мы ее прочитаем и выставим все настройки.

WriteSettings
bool WriteSettings(int n) {     std::wstring valueStr = std::to_wstring(n);     HKEY hKey; //открываем раздел в реестре      if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS)         return false; //RegSetValueEx Устанавливает данные и тип указанного значения в ключе реестра. Если значение с таким именем ещё не присутствует в ключе, функция добавляет его.      LONG result = RegSetValueEx(         hKey,         SETTINGS_MONITOR_NAME,         0,         REG_SZ,         reinterpret_cast<const BYTE *>(valueStr.c_str()),         static_cast<DWORD>((valueStr.size() + 1) * sizeof(wchar_t)));   //закрываем ключ     RegCloseKey(hKey);      if (result != ERROR_SUCCESS)         return false; //отправляем сообщение в броадкаст, чтобы все приложения увидели изменение в реестре     SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0,         (LPARAM)L"Environment",         SMTO_ABORTIFHUNG, 5000, nullptr);      return true; } 

Она принимает число, которое нужно записать в реестр.

ReadSettings
byte ReadSettings() {     HKEY hKey;     if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)         throw std::runtime_error("Couldnt open the key registry Environment");      wchar_t buffer[4];     DWORD bufferSize = sizeof(buffer);     DWORD type = 0;      LONG result = RegQueryValueExW(         hKey,         SETTINGS_MONITOR_NAME,         nullptr,         &type,         reinterpret_cast<LPBYTE>(buffer),         &bufferSize     );      RegCloseKey(hKey);      if (result != ERROR_SUCCESS)         throw std::runtime_error(std::string("variable was not found"));      if (type != REG_SZ)         throw std::runtime_error(std::string("variable has in incorrect format"));      try {         return (byte)std::stoi(buffer);     }     catch (...) {         throw std::runtime_error(std::string("Couldnt convert the value to a number"));     } }

Читает переменную из указанного раздела реестра

Теперь вернемся к блоку try-catche. Здесь мы пытаемся прочитать значение из переменной. Но если программа запущена впервые, то никакой переменной среды там не будет. В случае выброса ошибки при чтении запишем значение этой переменной, предварительно поднимем все флаги.

   try    {         settings_param = ReadSettings();    }    catch (std::runtime_error &msg)    {        settings_param = 0b0111;        WriteSettings(settings_param);    }

Заключение

Осталось добавить иконки нашего оконного приложения. Я просто взял иконку из первой части, перекрасил в фиолетовый цвет, а затем подтягивал ее в ресурсы .rc:

IDI_CPUSTATUSICON       ICON                    "small_icon.ico" IDI_SMALL               ICON                    "small_icon.ico"

Вот и финальный результат:

Без окна настроек

Без окна настроек
Окно настроек с отключенным CPU

Окно настроек с отключенным CPU

Вот и все. После того как она была готова, появлялись мысли о том, чтобы заняться ее продажей, но решил поделиться этим проектом с вами. Я учел предыдущие ошибки и постарался сделать эту статью более подробной и информативной. Ниже приведен исходный код программы. Можно скачать готовое решение на моем гитхабе.

Остается только закинуть .exe в папку автозагрузки

ПОЛНЫЙ КОД
#include "framework.h" #include "CPU_status_icon.h" #include "checkbox.h" #include <objidl.h> #include <gdiplus.h> #include <windows.h> #include <shellapi.h> #include <iostream> #include <string> #include <map>  #pragma comment(lib, "gdiplus.lib")  #pragma comment(lib, "Ole32.lib")  #define MAX_LOADSTRING 100 #define ICON_SIZE 64 #define ICON_CLICK (WM_APP + 1) #define CPU_CLICK (WM_APP + 2) #define RAM_CLICK (WM_APP + 3) #define OPEN_AT_START (WM_APP + 4) #define SETTINGS_MONITOR_NAME L"SETTINGS_MONITOR" #define RAM_COLOR Gdiplus::Color(255, 200, 90, 240)  // Глобальные переменные: HINSTANCE hInst; WCHAR szTitle[MAX_LOADSTRING]; WCHAR szWindowClass[MAX_LOADSTRING]; HANDLE htimer_ram = NULL; HANDLE htimer_cpu = NULL; NOTIFYICONDATA cpu_status_icon; NOTIFYICONDATA ram_status_icon; MEMORYSTATUSEX mem_info; ULONG_PTR g_gdiplusToken = 0; std::map<uint16_t, HICON> hash_cpu_icons; std::map<uint16_t, HICON> hash_ram_icons;  byte show_window_flag; byte settings_param = 0b0111; byte cpu_show = 0x2; byte ram_show = 0x2;  Checkbox *cpu_checkbox; Checkbox *ram_checkbox; Checkbox *open_start_checkbox;   ATOM                MyRegisterClass(HINSTANCE hInstance); BOOL                InitInstance(HINSTANCE, int); LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);  Gdiplus::Color GetGradColor(double x) {     double u = x / 50.0;     Gdiplus::Color c(         255, //alfa         int(255 * min(u, 1.0)), //r         int(255 - 90 * min(u, 1.0) - 165 * max(u - 1.0, 0.0)), //g         0);  //b     return c; }  void InitGDIPlus() {     Gdiplus::GdiplusStartupInput gdiplusStartupInput;     Gdiplus::GdiplusStartup(&g_gdiplusToken, &gdiplusStartupInput, nullptr); }  void ShutdownGDIPlus() {     if (g_gdiplusToken != 0)         Gdiplus::GdiplusShutdown(g_gdiplusToken); }  HICON CreateIconWithNumber(int number, int size, int fontSize, Gdiplus::Color textColor) {     std::wstring text = std::to_wstring(number);      Gdiplus::Bitmap bitmap(size, size, PixelFormat32bppARGB);     Gdiplus::Graphics graphics(&bitmap);     graphics.Clear(Gdiplus::Color(0, 0, 0, 0));      Gdiplus::FontFamily fontFamily(L"Segoe UI");     Gdiplus::Font font(&fontFamily, static_cast<Gdiplus::REAL>(fontSize), Gdiplus::FontStyleBold, Gdiplus::UnitPixel);     Gdiplus::SolidBrush brush(textColor);      Gdiplus::RectF charRect;     graphics.MeasureString(L"0", 1, &font, Gdiplus::PointF(0, 0), &charRect);     float y = (size - charRect.Height) / 2.0f;      float x = 0.0f;     float spacingFactor = 0.65f;     for (wchar_t ch : text)     {         wchar_t str[2] = { ch, 0 };         graphics.MeasureString(str, 1, &font, Gdiplus::PointF(0, 0), &charRect);         graphics.DrawString(str, 1, &font, Gdiplus::PointF(x, y), &brush);         x += charRect.Width * spacingFactor;     }      HICON hIcon = nullptr;     bitmap.GetHICON(&hIcon);     return hIcon; }  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; }  void CALLBACK TimerRAM(PVOID pVoid, BOOLEAN TimerOrWaitFired) {     static wchar_t ram_info[64];     ZeroMemory(ram_info, 64);     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;         HICON icon;         if (hash_ram_icons.find((uint16_t)usagePercent) == hash_ram_icons.end())         {             icon = CreateIconWithNumber(usagePercent, ICON_SIZE, 50, RAM_COLOR);             hash_ram_icons.insert({ (uint16_t)usagePercent, icon});         }         ram_status_icon.hIcon = hash_ram_icons[(uint16_t)usagePercent];         swprintf(ram_info, 64, 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];     ZeroMemory(cpu_inf, 8);     double cpu_total = GetCPULoad();     HICON icon;     if (hash_cpu_icons.find((uint16_t)cpu_total) == hash_cpu_icons.end())     {         icon = CreateIconWithNumber((uint16_t)cpu_total, ICON_SIZE, 50, GetGradColor(cpu_total));         hash_cpu_icons.insert({ (uint16_t)cpu_total,  icon });     }     cpu_status_icon.hIcon = hash_cpu_icons[(uint16_t)cpu_total];     swprintf(cpu_inf, 8, L"%.2f %%", cpu_total);     lstrcpy(cpu_status_icon.szTip, cpu_inf);     Shell_NotifyIcon(NIM_MODIFY, &cpu_status_icon); }  bool WriteSettings(int n) {     std::wstring valueStr = std::to_wstring(n);     HKEY hKey;      if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS)         return false;      LONG result = RegSetValueEx(         hKey,         SETTINGS_MONITOR_NAME,         0,         REG_SZ,         reinterpret_cast<const BYTE *>(valueStr.c_str()),         static_cast<DWORD>((valueStr.size() + 1) * sizeof(wchar_t)));     RegCloseKey(hKey);      if (result != ERROR_SUCCESS)         return false;      SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0,         (LPARAM)L"Environment",         SMTO_ABORTIFHUNG, 5000, nullptr);      return true; }  byte ReadSettings() {     HKEY hKey;     if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)         throw std::runtime_error("Couldnt open the key registry Environment");      wchar_t buffer[4];     DWORD bufferSize = sizeof(buffer);     DWORD type = 0;      LONG result = RegQueryValueExW(         hKey,         SETTINGS_MONITOR_NAME,         nullptr,         &type,         reinterpret_cast<LPBYTE>(buffer),         &bufferSize     );      RegCloseKey(hKey);      if (result != ERROR_SUCCESS)         throw std::runtime_error(std::string("variable was not found"));      if (type != REG_SZ)         throw std::runtime_error(std::string("variable has in incorrect format"));      try {         return (byte)std::stoi(buffer);     }     catch (...) {         throw std::runtime_error(std::string("Couldnt convert the value to a number"));     } }  int APIENTRY wWinMain(_In_ HINSTANCE hInstance,                      _In_opt_ HINSTANCE hPrevInstance,                      _In_ LPWSTR    lpCmdLine,                      _In_ int       nCmdShow) {     UNREFERENCED_PARAMETER(hPrevInstance);     UNREFERENCED_PARAMETER(lpCmdLine);      // TODO: Разместите код здесь.      // Инициализация глобальных строк     LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);     LoadStringW(hInstance, IDC_CPUSTATUSICON, szWindowClass, MAX_LOADSTRING);     MyRegisterClass(hInstance);      if (!InitInstance (hInstance, nCmdShow))     {         return FALSE;     }      HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CPUSTATUSICON));      MSG msg;      // Цикл осовного сообщения:     while (GetMessage(&msg, nullptr, 0, 0))     {         if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))         {             TranslateMessage(&msg);             DispatchMessage(&msg);         }     }      return (int) msg.wParam; }  ATOM MyRegisterClass(HINSTANCE hInstance) {     WNDCLASSEXW wcex;      wcex.cbSize = sizeof(WNDCLASSEX);      wcex.style          = CS_HREDRAW | CS_VREDRAW;     wcex.lpfnWndProc    = WndProc;     wcex.cbClsExtra     = 0;     wcex.cbWndExtra     = 0;     wcex.hInstance      = hInstance;     wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(1));     wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);     wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);     wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_CPUSTATUSICON);     wcex.lpszClassName  = szWindowClass;     wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));      return RegisterClassExW(&wcex); }  BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {    hInst = hInstance;     HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,       300, 300, 300, 300, nullptr, nullptr, hInstance, nullptr);     if (!hWnd)    {       return FALSE;    }     try    {         settings_param = ReadSettings();    }    catch (std::runtime_error &msg)    {        settings_param = 0b0111;        WriteSettings(settings_param);    }     cpu_checkbox = new Checkbox(hWnd, hInstance, CPU_CLICK, L"CPU", 100, 50, 100, 30);    ram_checkbox = new Checkbox(hWnd, hInstance, RAM_CLICK, L"RAM", 100, 100, 100, 30);    open_start_checkbox = new Checkbox(hWnd, hInstance, OPEN_AT_START, L"Open at start", 100, 150, 110, 30);     InitGDIPlus();    mem_info.dwLength = sizeof(mem_info);     cpu_status_icon.cbSize = sizeof(NOTIFYICONDATA);    cpu_status_icon.hWnd = hWnd;    cpu_status_icon.uID = 1;    cpu_status_icon.uCallbackMessage = ICON_CLICK;    cpu_status_icon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;     ram_status_icon.cbSize = sizeof(NOTIFYICONDATA);    ram_status_icon.hWnd = hWnd;    ram_status_icon.uID = 2;    ram_status_icon.uCallbackMessage = ICON_CLICK;    ram_status_icon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;      if (settings_param & 0b0100)    {        show_window_flag = 5;        open_start_checkbox->Check();    }    if (settings_param & 0b0010)    {        ram_show = 0;        ram_checkbox->Check();    }    if (settings_param & 0b0001)    {        cpu_show = 0;        cpu_checkbox->Check();    }     ShowWindow(hWnd, show_window_flag);    Shell_NotifyIcon(cpu_show, &cpu_status_icon);    Shell_NotifyIcon(ram_show, &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 ICON_CLICK:         if (lParam == WM_LBUTTONDOWN)         {             show_window_flag ^= 5;             ShowWindow(hWnd, show_window_flag);         }         break;     case WM_CLOSE:         show_window_flag = 0;         ShowWindow(hWnd, show_window_flag);         break;     case WM_COMMAND:         if (HIWORD(wParam) == BN_CLICKED)         {             switch (LOWORD(wParam))             {                 case CPU_CLICK:                     cpu_checkbox->Revers();                     cpu_show ^= 0x2;                     settings_param ^= 0b001;                     Shell_NotifyIcon(cpu_show, &cpu_status_icon);                     open_start_checkbox->UnBlock();                     WriteSettings(settings_param);                     break;                 case RAM_CLICK:                     ram_checkbox->Revers();                     ram_show ^= 0x2;                     settings_param ^= 0b010;                     Shell_NotifyIcon(ram_show, &ram_status_icon);                     open_start_checkbox->UnBlock();                     WriteSettings(settings_param);                     break;                 case OPEN_AT_START:                     open_start_checkbox->Revers();                     settings_param ^= 0b100;                     WriteSettings(settings_param);                     break;                 }                 if (!(settings_param & 1) && !(settings_param & 2))                 {                     settings_param = 0b100;                     open_start_checkbox->Check();                     open_start_checkbox->Block();                     WriteSettings(settings_param);                 }         }         break;     case WM_DESTROY:         ShutdownGDIPlus();         Shell_NotifyIcon(NIM_DELETE, &cpu_status_icon);         Shell_NotifyIcon(NIM_DELETE, &ram_status_icon);         DeleteTimerQueueTimer(NULL, htimer_cpu, NULL);         DeleteTimerQueueTimer(NULL, htimer_ram, NULL);         PostQuitMessage(0);         break;     default:         return DefWindowProc(hWnd, message, wParam, lParam);     }     return 0; }

Не забудьте добавить checkbox.h!!!

Мой гитхаб!


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


Комментарии

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

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