Финансы Сайт на котором знают все про финансы

Добавляем в 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: скачать