Как сплагиатить удобную утилиту для показа зарядки ноутбука?
Всё началось с того что я увидел утилиту от 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.
- HPOWERNOTIFY WINAPI RegisterPowerSettingNotification(
- __in HANDLE hRecipient,
- __in LPCGUID PowerSettingGuid,
- __in DWORD Flags
- );
Эта функция поддерживает как работу в сервисах так и в обычных приложениях(чем мы и воспользуемся).
Для начала я скопировал пример работы с DeskBand в отдельную папку и начал его править.
Вызываю я её в обработчике события WM_CREATE вот так:
- h1 = RegisterPowerSettingNotification(pDeskBand->m_hwnd, &GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
- if (h1== 0 ) pDeskBand->CloseDW( 1 );
- h2 = RegisterPowerSettingNotification(pDeskBand->m_hwnd, &GUID_BATTERY_PERCENTAGE_REMAINING, DEVICE_NOTIFY_WINDOW_HANDLE);
- if (h2== 0 ) CloseDW( 1 );
- >
h1 и h2 — глобальные(ногами не бить!) переменные типа HPOWERNOTIFY, что есть те же HANDLE по сути своей. Так мы подписались на получение событий вставки/вынимания кабеля зарядки и изменения оставшегося процента батареи. Так же можно подписаться и на изменение схемы питания используя GUID_POWERSCHEME_PERSONALITY. Кстати, не забудьте в деструкторе например сделать UnregisterPowerSettingNotification для h1 и h2.
Так же я завёл глобальные(только не по почкам!) переменные для хранения заряда батареи и статуса зарядки:
- bool isOnBattery= false ;
- int charged=-1;
И в обработчике приходящего события WM_POWERBROADCAST:
- POWERBROADCAST_SETTING* pps = (POWERBROADCAST_SETTING*)lParam;
- if ( sizeof ( int ) == pps->DataLength && pps->PowerSetting == GUID_ACDC_POWER_SOURCE ) Data;
- isOnBattery= ( 0 != nPowerSrc);
- >
- else if ( sizeof ( int ) == pps->DataLength && pps->PowerSetting == GUID_BATTERY_PERCENTAGE_REMAINING ) Data;
- >
- pDeskBand->OnPaint(NULL);
Что сие делает? При получении события WM_POWERBROADCAST в lParam передается указатель на структуру POWERBROADCAST_SETTING, которую мы и получаем. Т.к. сама эта структура тоже в меру извращенная, то мы смотрим какому событию соответствует именно эта заполненная структура.
Далее если это событие подключения зарядки — то мы смотрим что получилось. 0 — это батарея, 1 — зарядка, 2 — UPS(ни разу не видел чтобы сабж где-то засветился). Ну и сохраняем результат в переменную isOnBattery(true — если от батареи, false — иначе).
Если же это событие изменения % зарядки батареи — то сохраняем уровень зарядки себе. Далее вызываем перерисовку.
Для начала надо определиться что будем рисовать. Мне было лень искать красивые картинки батареек, поэтому я решил просто вывести красиво текст. Так что сначала я формирую строку:
- wchar_t* arr = (wchar_t*)malloc(1024); //не забудьте потом сделать free (arr);
- if (charged>=0)
- _itow(charged, arr,10);
- else
- wcscpy(arr,L"?");
- wcscat(arr,L"%");
- if (!isOnBattery) wcscat(arr,L" (+)");
Далее не забываю сказать, что я вообще-то перерисовываюсь…
- InvalidateRect(m_hwnd,&rc, true );
Далее два случая — включена композиция рабочего стола или нет. Т.к. я старой темой Windows не пользуюсь и предпочитаю Aero — то в случае отключенной композиции просто вывожу текст:
- SetBkColor(hdc, RGB(255, 255, 0));
- GetTextExtentPointW(hdc, arr, wcslen(arr), & size );
- TextOutW(hdc, (RECTWIDTH(rc) - size .cx) / 2, (RECTHEIGHT(rc) - size .cy) / 2, arr, wcslen(arr));
В случае включенной композиции немного сложнее:
- DTTOPTS dttOpts = < sizeof (dttOpts)>;
- dttOpts.dwFlags = DTT_COMPOSITED | DTT_TEXTCOLOR | DTT_GLOWSIZE;
- int red= 0 ;
- int green= 0 ;
- if (charged> 0 ) 66 ) green = 255 ;
- else if (charged> 33 ) green = red = 255 ;
- else red = 255 ;
- >
- dttOpts.crText = RGB(red, green, 0 );
- dttOpts.iGlowSize = 10 ;
- DrawThemeTextEx(hTheme, hdcPaint, 0 , 0 , arr, - 1 , 0 , &rcText, &dttOpts);
Собственно, тут я считаю, каким цветом рисовать зарядку(66-100% = зеленый, 33-66% = желтый, 0-33% = красный), и вывожу, соответственно, текст.
Вот и собственно всё. Можно собирать проект. Хочу отметить, что тут я не указывал тот код, что был в примере, только тот, что изменялся или дописывался мной. После успешной сборки проекта я получил dll-ку.