Добавляем в LastFM управление через панель задач
С появлением Windows 7 в системе появилась полезная фишка, позволяющая размещать элементы управления в панели задач, а именно в превью, которое высвечивается при наведении на элемент в панели. Однако, редко встретишь приложение, которое её использует. Из множества приложений, которыми я пользуюсь, на ум приходит только одно – Media Player Classic. Вот так выглядят элементы управления для него:
Так сложилось, что я люблю слушать “полу-радио”, в частности, Last.fm. Но, к сожалению, в клиенте Last.fm нет поддержки этих модных кнопочек, поэтому мне захотелось добавить их в него собственноручно. Перечень действий, которые необходимо для этого, примерно следующий:
1. Находим хендл основного окна клиента.
2. Добавляем элементы управления.
3. Ставим свой обработчик оконных сообщений, в котором задаем поведение элементов управления.
Итак, приступим. Для начала, инклюды и файл ресурсов:
#include //В этом файле определены методы, позволяющие добавить нужные элементы управления #include #include #include "resource.h" //Необходима, т.к. используются COM-интерфейсы #pragma comment(lib, "Comctl32")
Файл ресурсов и несколько констант, которые понадобятся дальше:
//В buttons.bmp хранятся пиктограммы кнопочек BUTTONS_BMP BITMAP "buttons.bmp"
#define BUTTONS_BMP 101 #define BUTTON1 31337 #define BUTTON2 31338 #define BUTTON3 31339
Теперь перейдем к основному коду. Во-первых, объявим несколько глобальных переменных и один полезный макрос:
#define MAKEDWORD(lo, hi) ((DWORD)(((WORD)(lo)) | ((DWORD)((WORD)(hi))) << 16)) HINSTANCE ghInstance; HWND ghWnd; WNDPROC old_proc;
Далее реализуем функцию, которая будет “нажимать” кнопки по заданным координатам:
void ClickAt(HWND hWnd, int x, int y) { SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKEDWORD(x, y)); SendMessage(hWnd, WM_LBUTTONUP, 0, MAKEDWORD(x, y)); }
Почему именно эмуляция нажатия? Дело в том, что клиент Last.fm написан с использованием Qt, а там своеобразная модель взаимодействия элементов интерфейса, отличная от стандартной виндовой, поэтому вместо того, чтобы искать хендлы кнопок и посылать команды им, я упростил себе задачу и остановился на этом варианте. Тем более, положение элементов управления в окне (для которых мы будем делать “бинды”) не меняется при ресайзе.
Пришла очередь обработчика оконных сообщений:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { if(msg == WM_COMMAND) { //Определяем, какая кнопка была нажата int const ButtonID = LOWORD(wParam); switch(ButtonID) { //Запустить/Остановить проигрывание case BUTTON1: ClickAt(hWnd, 447, 72); break; //Пропустить композицию case BUTTON2: ClickAt(hWnd, 514, 72); break; //Добавить композицию в список любимых case BUTTON3: ClickAt(hWnd, 318, 72); break; } } return CallWindowProc(old_proc, hWnd, msg, wParam, lParam); }
Идентификатора кнопки получается из wParam, так как того требует MSDN.
Теперь самая главная функция, которая добавит элементы управления для окна с указанным хендлом.
HRESULT CreateThumbnailToolbar(HWND hWnd) { ITaskbarList4 *pTaskbarList; HRESULT hr = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pTaskbarList)); if (SUCCEEDED(hr)) { //Инициализируем таскбар hr = pTaskbarList->HrInit(); if (SUCCEEDED(hr)) { //Формируем список изображений из ресурса, при этом размер каждого элемента списка будет 20x20 пикселей HIMAGELIST himl = ImageList_LoadImage(ghInstance, MAKEINTRESOURCE(BUTTONS_BMP), 20, 0, RGB(255,0,255), IMAGE_BITMAP, LR_CREATEDIBSECTION); if (himl) { //Задаем список изображений, которые будут использоваться для кнопок hr = pTaskbarList->ThumbBarSetImageList(hWnd, himl); if (SUCCEEDED(hr)) { THUMBBUTTON buttons[3] = {}; //Задаем параметры для трех кнопок buttons[0].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[0].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; buttons[0].iId = BUTTON1; buttons[0].iBitmap = 0; StringCchCopy(buttons[0].szTip, ARRAYSIZE(buttons[0].szTip), L"Start/Stop"); buttons[1].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[1].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; buttons[1].iId = BUTTON2; buttons[1].iBitmap = 1; StringCchCopy(buttons[1].szTip, ARRAYSIZE(buttons[1].szTip), L"Skip"); buttons[2].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[2].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; buttons[2].iId = BUTTON3; buttons[2].iBitmap = 2; StringCchCopy(buttons[2].szTip, ARRAYSIZE(buttons[2].szTip), L"Like"); //Добавляем элементы управления hr = pTaskbarList->ThumbBarAddButtons(hWnd, ARRAYSIZE(buttons), buttons); } ImageList_Destroy(himl); } } pTaskbarList->Release(); } return hr; }
Основной этап пройден, осталось несколько мелких функций. Функция поиска хендла главного окна процесса:
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) { //Проверяем установлен ли стиль WS_VISIBLE if((GetWindowLong(hWnd, GWL_STYLE) & WS_VISIBLE)) { DWORD pid; GetWindowThreadProcessId(hWnd, &pid); //Сравниваем id текущего процесса (передается в lParam) и id процесса полученного хендла if(pid == lParam) { ghWnd = hWnd; return FALSE; } } return TRUE; }
Оставшаяся часть кода:
void ThreadProc() { //Ждем создания основного окна Sleep(5000); //Инициализируем COM-библиотеку CoInitialize(NULL); //Ищем основное окно EnumWindows(&EnumWindowsProc, GetCurrentProcessId()); //Создаем тулбар для найденного хендла CreateThumbnailToolbar(ghWnd); //Сохраняем адрес старого обработчика оконных сообщений и заменяем его на свой old_proc = (WNDPROC)GetWindowLong(ghWnd, GWL_WNDPROC); SetWindowLong(ghWnd, GWL_WNDPROC, (LONG)WndProc); ExitThread(0); } BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID reserved) { if(dwReason == DLL_PROCESS_ATTACH) { ghInstance = hInstance; //Запускаем поток, так как необходимо дать приложению загрузиться и создать окно, что произойдет только после загрузки DLL CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); } else if(dwReason == DLL_PROCESS_DETACH) { CoUninitialize(); } return TRUE; } //Пустая экспортируемая функция, которую мы добавим в импорт к LastFM.exe __declspec(dllexport) void __cdecl dummy(void) {};
Теперь у нас есть готовая DLL, которую можно добавить в импорт к клиенту и получить желаемые элементы управления. В качестве иконок для элементов управления я использовал оригинальные изображения из клиента. Извлечь их довольно просто:
1. Смотрим где находится секция данных, например, с помощью CFF Explorer.
2. Пролистываем секцию в поисках заголовков стандартных изображений (в нашем случае PNG). И разрезаем на отдельные файлы, например, с помощью WinHex.
Теперь из них формируем файл размером 60х20 пикселей, и шаблон для кнопок готов. В результате получится примерно так:
Исходные коды и скомпилированная DLL: скачать