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

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

Всё началось с того что я увидел утилиту от 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.

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

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

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

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

  1. h1 = RegisterPowerSettingNotification(pDeskBand->m_hwnd, &GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE);
  2. if (h1== 0 ) pDeskBand->CloseDW( 1 );
  3. h2 = RegisterPowerSettingNotification(pDeskBand->m_hwnd, &GUID_BATTERY_PERCENTAGE_REMAINING, DEVICE_NOTIFY_WINDOW_HANDLE);
  4. if (h2== 0 )
  5. CloseDW( 1 );
  6. >

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

Так же я завёл глобальные(только не по почкам!) переменные для хранения заряда батареи и статуса зарядки:

  1. bool isOnBattery= false ;
  2. int charged=-1;

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

  1. POWERBROADCAST_SETTING* pps = (POWERBROADCAST_SETTING*)lParam;
  2. if ( sizeof ( int ) == pps->DataLength && pps->PowerSetting == GUID_ACDC_POWER_SOURCE )
  3. Data;
  4. isOnBattery= ( 0 != nPowerSrc);
  5. >
  6. else if ( sizeof ( int ) == pps->DataLength && pps->PowerSetting == GUID_BATTERY_PERCENTAGE_REMAINING )
  7. Data;
  8. >
  9. pDeskBand->OnPaint(NULL);

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

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

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

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

  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" (+)");

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

  1. InvalidateRect(m_hwnd,&rc, true );

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

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

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

  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. 66 ) green = 255 ;
  7. else if (charged> 33 ) green = red = 255 ;
  8. else red = 255 ;
  9. >
  10. dttOpts.crText = RGB(red, green, 0 );
  11. dttOpts.iGlowSize = 10 ;
  12. DrawThemeTextEx(hTheme, hdcPaint, 0 , 0 , arr, - 1 , 0 , &rcText, &dttOpts);

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

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

📎📎📎📎📎📎📎📎📎📎