Как стать автором
Обновить

Как сплагиатить удобную утилиту для показа зарядки ноутбука?

Время на прочтение4 мин
Количество просмотров2.2K
Всё началось с того что я увидел утилиту от IBM/Lenovo для показа заряда батареи ноутбука в непривычном месте — в таскбаре/супербаре, но не в виде значка, а как панель(аналогичные используются для управления проигрывателями iTunes, WMP, Zune):



Т.к. у меня ноутбуки другого производителя, а искать как выцарапать эту софтину у производителя мне было лень — я начал искать аналог, и, к преглубокому удивлению, ничего не нашел! (если я не прав — покажите носом, буду весьма благодарен!)

Именно так я решил написать своё решение. Писать будем на C++. Я писал в Visual Studio 2010, можно использовать предыдущие версии. Главное — наличие установленного Windows SDK(ставится отдельно от студии, доступен бесплатно, скачать можно например тут)

Вот что у меня получилось:

Моя панелька

Больше всего времени у меня занял поиск в интернете названия собственно того куда я хотел запихать отображение данных. Оно называется DeskBand, и что самое хорошее — в Windows SDK есть пример работы его! (у меня он по адресу C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\winui\shell\shellextensibility\deskbands).

Далее дело стояло за малым — надо найти как получать информацию о заряде батареи. Поисковик в сочетании с MSDN навёл меня на полезную функцию — RegisterPowerSettingNotification. Её минус — она поддерживается только начиная с Windows Vista, но меня это не останавливало, т.к. я писал для себя и лично я уже везде пользуюсь Windows 7.

Вот её синтакс:

Copy Source | Copy HTML
  1. HPOWERNOTIFY WINAPI RegisterPowerSettingNotification(
  2. __in HANDLE hRecipient,
  3. __in LPCGUID PowerSettingGuid,
  4. __in DWORD Flags
  5. );
  6.  


Эта функция поддерживает как работу в сервисах так и в обычных приложениях(чем мы и воспользуемся).

Для начала я скопировал пример работы с DeskBand в отдельную папку и начал его править.

Вызываю я её в обработчике события WM_CREATE вот так:

Copy Source | Copy HTML
  1. h1 = RegisterPowerSettingNotification(pDeskBand->m_hwnd, &GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
  2. if (h1== 0) pDeskBand->CloseDW(1);
  3.  
  4. h2 = RegisterPowerSettingNotification(pDeskBand->m_hwnd, &GUID_BATTERY_PERCENTAGE_REMAINING, DEVICE_NOTIFY_WINDOW_HANDLE);
  5. if (h2== 0)
  6. {
  7. UnregisterPowerSettingNotification(h1);
  8. pDeskBand->CloseDW(1);
  9. }
  10.  
  11.  


h1 и h2 — глобальные(ногами не бить!) переменные типа HPOWERNOTIFY, что есть те же HANDLE по сути своей.
Так мы подписались на получение событий вставки/вынимания кабеля зарядки и изменения оставшегося процента батареи. Так же можно подписаться и на изменение схемы питания используя GUID_POWERSCHEME_PERSONALITY.
Кстати, не забудьте в деструкторе например сделать UnregisterPowerSettingNotification для h1 и h2.

Так же я завёл глобальные(только не по почкам!) переменные для хранения заряда батареи и статуса зарядки:
Copy Source | Copy HTML
  1. bool isOnBattery=false;
  2. int charged=-1;
  3.  


И в обработчике приходящего события WM_POWERBROADCAST:

Copy Source | Copy HTML
  1. POWERBROADCAST_SETTING* pps = (POWERBROADCAST_SETTING*)lParam;
  2. if ( sizeof(int) == pps->DataLength && pps->PowerSetting == GUID_ACDC_POWER_SOURCE )
  3. {
  4. int nPowerSrc = *(int*)(DWORD_PTR) pps->Data;
  5. isOnBattery= ( 0 != nPowerSrc);
  6. }
  7. else if ( sizeof(int) == pps->DataLength && pps->PowerSetting == GUID_BATTERY_PERCENTAGE_REMAINING )
  8. {
  9. charged = *(int*)(DWORD_PTR) pps->Data;
  10. }
  11. pDeskBand->OnPaint(NULL);
  12.  


Что сие делает? При получении события WM_POWERBROADCAST в lParam передается указатель на структуру POWERBROADCAST_SETTING, которую мы и получаем. Т.к. сама эта структура тоже в меру извращенная, то мы смотрим какому событию соответствует именно эта заполненная структура.

Далее если это событие подключения зарядки — то мы смотрим что получилось. 0 — это батарея, 1 — зарядка, 2 — UPS(ни разу не видел чтобы сабж где-то засветился). Ну и сохраняем результат в переменную isOnBattery(true — если от батареи, false — иначе).

Если же это событие изменения % зарядки батареи — то сохраняем уровень зарядки себе. Далее вызываем перерисовку.

Для начала надо определиться что будем рисовать. Мне было лень искать красивые картинки батареек, поэтому я решил просто вывести красиво текст. Так что сначала я формирую строку:

Copy Source | Copy HTML
  1. wchar_t* arr = (wchar_t*)malloc(1024); //не забудьте потом сделать free(arr);
  2. if (charged>=0)
  3. _itow(charged, arr,10);
  4. else
  5. wcscpy(arr,L"?");
  6. wcscat(arr,L"%");
  7. if (!isOnBattery) wcscat(arr,L" (+)");
  8.  


Далее не забываю сказать, что я вообще-то перерисовываюсь…

Copy Source | Copy HTML
  1. InvalidateRect(m_hwnd,&rc,true);
  2.  


Далее два случая — включена композиция рабочего стола или нет. Т.к. я старой темой Windows не пользуюсь и предпочитаю Aero — то в случае отключенной композиции просто вывожу текст:

Copy Source | Copy HTML
  1. SetBkColor(hdc, RGB(255, 255, 0));
  2. GetTextExtentPointW(hdc, arr, wcslen(arr), &size);
  3. TextOutW(hdc, (RECTWIDTH(rc) - size.cx) / 2, (RECTHEIGHT(rc) - size.cy) / 2, arr, wcslen(arr));


В случае включенной композиции немного сложнее:

Copy Source | Copy HTML
  1. DTTOPTS dttOpts = {sizeof(dttOpts)};
  2. dttOpts.dwFlags = DTT_COMPOSITED | DTT_TEXTCOLOR | DTT_GLOWSIZE;
  3. int red= 0;
  4. int green= 0;
  5. if (charged> 0)
  6. {
  7. if (charged>66) green = 255;
  8. else if (charged>33) green = red = 255;
  9. else red = 255;
  10. }
  11. dttOpts.crText = RGB(red, green,  0);
  12. dttOpts.iGlowSize = 10;
  13.  
  14. DrawThemeTextEx(hTheme, hdcPaint,  0,  0, arr, -1,  0, &rcText, &dttOpts);


Собственно, тут я считаю, каким цветом рисовать зарядку(66-100% = зеленый, 33-66% = желтый, 0-33% = красный), и вывожу, соответственно, текст.

Вот и собственно всё. Можно собирать проект.
Хочу отметить, что тут я не указывал тот код, что был в примере, только тот, что изменялся или дописывался мной.
После успешной сборки проекта я получил dll-ку.

По опыту работы с COM-компонентами и подобной гадостью — я выполнил от админа следующую комманду в папке с полученной DLL-кой:
regsvr32 DeskbandSDKSample.dll


Далее я просто включил соответствующую панель через контекстное меню.

Вот и всё.

Вопросы?
Теги:
Хабы:
Всего голосов 40: ↑36 и ↓4+32
Комментарии40

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань