Однажды, в 2009 году, вышла Windows 7. В то время я сидел на Висте, которая притормаживала на моем стареньком компьютере, и я решил пересесть на семерку сразу после ее выхода.
Первое, на что я обратил внимание после установки — новая панель задач. А конкретнее — тот факт, что она группирует кнопки по программе, к которой они принадлежат.
Сразу же полез в настройки, чтобы отключить это безобразие, и с удивлением обнаружил, что группировка не отключается. Наиболее близкий к желаемому вариант, Never combine, кнопки все же группирует.
Я думал, что как и мне, многим это не понравится, и был уверен, что спустя неделю-две в интернете всплывет решение этой проблемы. Но я так ничего путного и не нашел, и понял, что придется действовать самому.
Под катом:
Для понимания описанного ниже процесса, желательно иметь базовое знание ассемблера.
Для того, чтобы отучить панель задач от группировки, нам понадобится OllyDbg — бесплатный проприетарный 32-битный отладчик уровня ассемблера для операционных систем Windows, предназначенный для анализа и модификации откомпилированных исполняемых файлов и библиотек, работающих в режиме пользователя (ring-3) © Wikipedia.
Повторить процесс можно на ОС Windows 7 или Windows 8. К сожалению, на данный момент имеется только 32-битная версия OllyDbg, так что если у вас 64-битная ОС, повторить следующие действия у вас не получится (в таком случае можно использовать виртуальную машину).
OllyDbg — портативная программа, и в инсталляции не нуждается. Создайте папку с правами записи, и перепишите туда файлы с этого архива.
Microsoft Symbol Server — Майкрософтовский сервер отладочных символов, благодаря которым мы, кроме чистого ассемблера, будем видеть еще и названия функций и переменных. Это очень помогает при анализе кода.
Для этого нужно сделать следующее:
Как известно, панель задач — часть процесса Windows Explorer. Запускаем OllyDbg, и выбираем File -> Attach.... Затем, выбираем explorer и жмем на Attach.
Attach, с английского — присоединять — то есть мы присоединяем наш отладчик к проводнику для его отладки.
Затем ждем, пока все модули загрузятся. Это может занять несколько минут, и в это время проводник не будет реагировать. После завершения загрузки в строке состояния справа будет написано Paused на желтом фоне — то есть процесс приостановлен. Жмем F9 чтобы возобновить работу процесса.
Внимание, исключения: если процесс приостанавливается, и в строке состояния появляется надпись Exception xxxxxxxx (как на скриншоте ниже), нажмите Shift+F9 чтобы передать исключение по назначению.
Теперь проводник работает под надзором нашего отладчика, OllyDbg. Если связь с сервером отладочных символов прошла как следует, мы можем взглянуть на функции проводника.
Сначала, откроем модуль процесса explorer в окошке CPU отладчика. Для этого откроем окошко модулей (буква E на голубом фоне), нажмем правой кнопкой на Explorer, и выберем View code in CPU.
Затем, нажмем правой кнопкой на код, и выберем Search for -> Names. Перед нами появится список функций, присутствующих в проводнике. Для более удобной работой со списком, отсортируем его по имени, скопируем и вставим в текст в любимый текстовой редактор.
Итак, нам нужно отключить группировку. Логично начать поиск со слова group. В глаза сразу бросаются два класса: CTaskBtnGroup и CTaskGroup. Вместе у этих двух классов 131 функций — не очень много для беглого просмотра названий.
Мне сложно сказать, можно ли найти нужную нам функцию, всего лишь изучая названия, так как я уже неплохо с многими из них знаком. В любом случае, нужная нам функция — CTaskGroup::DoesWindowMatch, название которой переводится как совпадает ли окошко, и делает она именно это — отвечает, подходит ли окошко к определенной группе.
Мы нашли функцию с интересным названием, теперь давайте посмотрим, чего она из себя представляет. Вернемся в OllyDbg, нажмем на Ctrl+G и перейдем на адрес функции (тот восьмизначный номер в начале строки, в нашем случае 00973629).
P.S. Советую включить подсветку Jumps and calls, благодаря которой четко видны вызовы функций и условные/безусловные прыжки.
Итак, вот наша функция:
Столбцы, слева на право: адрес, байты, команды (ассемблер), комментарии.
Голубым цветом помечены функции, желтым — переходы. На них мы и сосредоточимся.
Первым делом, вызывается какая-то функция, и в зависимости от результата происходит переход. Здесь я на них задерживаться не буду, замечу только, что к нашей задачи они дело не имеют.
Дальше мы видим три перехода. Первый перепрыгивает через второго и третьего. Второй и третий переходят куда-то подальше — давайте посмотрим, куда…
Первый
В глаза сразу бросается WinAPI функция ILIsEqual, которая сравнивает две структуры. После некоторого анализа становится ясно, что сравниваемые структуры связаны с группами. Наша цель — отменить любую группировку, так что пропатчим код так, чтобы проводник думал, что структуры не равны.
Для этого, пропишем безусловный прыжок JMP, как на скриншоте сверху.
Второй
Как и в первом разе, сразу видим WinAPI функцию для сравнения, на этот раз текста — CompareStringOrdinal. Опять же, после экспериментирования становится ясно, что и это сравнение связано с группами. На этот раз сравниваются так называемые Application ID — идентификатор аппликации, по которым панель задач группирует кнопки.
Опять же, ставим безусловный прыжок, заставляя проводника думать, что все идентификаторы разные.
Большое преимущество отладчика от, например, простого дисассемблера в том, что изменения сразу вступают в силу, так как модифицируем мы память запущенного процесса напрямую. По этой же причине нужно быть очень осторожным, так как ошибка может привести к краху процесса.
Попробуем открыть несколько копий блокнота, и увидим, что они не группируются:
Получилось :)
Так как мы изменили код только в памяти запущенного процесса, наша модификация продержится только до завершения процесса.
Несмотря на то, что оригинальный язык данного кода — C++, ниже превидена самописная декомпиляция на чистом C.
Программа 7+ Taskbar Tweaker, автором которой являюсь я, умеет отключать группировку как глобально, так и выборочно по Application ID. Кроме этого, имеются в наличие еще несколько интересных настроек панели задач.
Иногда я открываю статью на хабре, которая кажется мне интересной, но после того как начинаю читать, понимаю что она не такая уж и интересная (или не очень понятная). В таких случаях я часто сразу перелистываю вниз к заключению/выводам.
Если вы находитесь здесь по этой же причине, расскажу вкратце о чем шла речь:
Я взял отладчик, и присоединил его к проводнику. Затем я нашел нужную функцию, и изменил ее так, чтобы проводник думал, что любое созданное окошко не подходит ни к одной из существующих групп. В связи с этим изменением, проводник перестал группировать кнопки на панели задач.
Первое, на что я обратил внимание после установки — новая панель задач. А конкретнее — тот факт, что она группирует кнопки по программе, к которой они принадлежат.
Сразу же полез в настройки, чтобы отключить это безобразие, и с удивлением обнаружил, что группировка не отключается. Наиболее близкий к желаемому вариант, Never combine, кнопки все же группирует.
Я думал, что как и мне, многим это не понравится, и был уверен, что спустя неделю-две в интернете всплывет решение этой проблемы. Но я так ничего путного и не нашел, и понял, что придется действовать самому.
Под катом:
- Подробная демонстрация того, как можно самостоятельно подправить принцип работы такого системного процесса, как explorer, под себя, используя отладчик OllyDbg.
- Готовое решение описанной выше проблемы.
Приступаем
Для понимания описанного ниже процесса, желательно иметь базовое знание ассемблера.
Для того, чтобы отучить панель задач от группировки, нам понадобится OllyDbg — бесплатный проприетарный 32-битный отладчик уровня ассемблера для операционных систем Windows, предназначенный для анализа и модификации откомпилированных исполняемых файлов и библиотек, работающих в режиме пользователя (ring-3) © Wikipedia.
Повторить процесс можно на ОС Windows 7 или Windows 8. К сожалению, на данный момент имеется только 32-битная версия OllyDbg, так что если у вас 64-битная ОС, повторить следующие действия у вас не получится (в таком случае можно использовать виртуальную машину).
Инсталляция OllyDbg
OllyDbg — портативная программа, и в инсталляции не нуждается. Создайте папку с правами записи, и перепишите туда файлы с этого архива.
Настройка Microsoft Symbol Server
Microsoft Symbol Server — Майкрософтовский сервер отладочных символов, благодаря которым мы, кроме чистого ассемблера, будем видеть еще и названия функций и переменных. Это очень помогает при анализе кода.
Для этого нужно сделать следующее:
- Скачайте этот архив, и поместите его содержание в папку программы OllyDbg.
Что это за файлы?- symsrv.dll — DLL файл Майкрософта, предназначен для работы с их сервером отладочных символов. Подписан Майкрософтом.
- symsrv.yes — пустой файл. Обозначает, что вы согласны с условиями предоставленных услуг.
- symsrv.ini — исключает все файлы кроме тех, которые начинаются на exp — для нашего explorer.exe. Иначе, если будут качаться все символы, придется очень долго ждать, да и не нужно оно нам для данной демонстрации.
- Включите OllyDbg, откройте настройки (Alt+O), и пометьте опцию Demangle symbolic names. Эта опция декодирует символических имена, делая их более читаемые для нас, людей.
Скриншот - В настройках, откройте страницу Debugging data, и пометьте Allow access to Microsoft Symbol Server. Затем создайте папку для символов, и выберете ее в настройках, как на скриншоте.
Скриншот - Перезапустите OllyDbg.
Присоединение процесса explorer
Как известно, панель задач — часть процесса Windows Explorer. Запускаем OllyDbg, и выбираем File -> Attach.... Затем, выбираем explorer и жмем на Attach.
Скриншоты
Attach, с английского — присоединять — то есть мы присоединяем наш отладчик к проводнику для его отладки.
Затем ждем, пока все модули загрузятся. Это может занять несколько минут, и в это время проводник не будет реагировать. После завершения загрузки в строке состояния справа будет написано Paused на желтом фоне — то есть процесс приостановлен. Жмем F9 чтобы возобновить работу процесса.
Внимание, исключения: если процесс приостанавливается, и в строке состояния появляется надпись Exception xxxxxxxx (как на скриншоте ниже), нажмите Shift+F9 чтобы передать исключение по назначению.
Скриншот
Обзор функций проводника
Теперь проводник работает под надзором нашего отладчика, OllyDbg. Если связь с сервером отладочных символов прошла как следует, мы можем взглянуть на функции проводника.
Сначала, откроем модуль процесса explorer в окошке CPU отладчика. Для этого откроем окошко модулей (буква E на голубом фоне), нажмем правой кнопкой на Explorer, и выберем View code in CPU.
Скриншот
Затем, нажмем правой кнопкой на код, и выберем Search for -> Names. Перед нами появится список функций, присутствующих в проводнике. Для более удобной работой со списком, отсортируем его по имени, скопируем и вставим в текст в любимый текстовой редактор.
Скриншоты
Итак, нам нужно отключить группировку. Логично начать поиск со слова group. В глаза сразу бросаются два класса: CTaskBtnGroup и CTaskGroup. Вместе у этих двух классов 131 функций — не очень много для беглого просмотра названий.
Мне сложно сказать, можно ли найти нужную нам функцию, всего лишь изучая названия, так как я уже неплохо с многими из них знаком. В любом случае, нужная нам функция — CTaskGroup::DoesWindowMatch, название которой переводится как совпадает ли окошко, и делает она именно это — отвечает, подходит ли окошко к определенной группе.
Скриншот
Просмотр выбранной функции
Мы нашли функцию с интересным названием, теперь давайте посмотрим, чего она из себя представляет. Вернемся в OllyDbg, нажмем на Ctrl+G и перейдем на адрес функции (тот восьмизначный номер в начале строки, в нашем случае 00973629).
P.S. Советую включить подсветку Jumps and calls, благодаря которой четко видны вызовы функций и условные/безусловные прыжки.
Скриншот
Итак, вот наша функция:
Столбцы, слева на право: адрес, байты, команды (ассемблер), комментарии.
Голубым цветом помечены функции, желтым — переходы. На них мы и сосредоточимся.
Первым делом, вызывается какая-то функция, и в зависимости от результата происходит переход. Здесь я на них задерживаться не буду, замечу только, что к нашей задачи они дело не имеют.
Дальше мы видим три перехода. Первый перепрыгивает через второго и третьего. Второй и третий переходят куда-то подальше — давайте посмотрим, куда…
Первый
В глаза сразу бросается WinAPI функция ILIsEqual, которая сравнивает две структуры. После некоторого анализа становится ясно, что сравниваемые структуры связаны с группами. Наша цель — отменить любую группировку, так что пропатчим код так, чтобы проводник думал, что структуры не равны.
Для этого, пропишем безусловный прыжок JMP, как на скриншоте сверху.
Второй
Как и в первом разе, сразу видим WinAPI функцию для сравнения, на этот раз текста — CompareStringOrdinal. Опять же, после экспериментирования становится ясно, что и это сравнение связано с группами. На этот раз сравниваются так называемые Application ID — идентификатор аппликации, по которым панель задач группирует кнопки.
Опять же, ставим безусловный прыжок, заставляя проводника думать, что все идентификаторы разные.
Пробуем
Большое преимущество отладчика от, например, простого дисассемблера в том, что изменения сразу вступают в силу, так как модифицируем мы память запущенного процесса напрямую. По этой же причине нужно быть очень осторожным, так как ошибка может привести к краху процесса.
Попробуем открыть несколько копий блокнота, и увидим, что они не группируются:
Получилось :)
Так как мы изменили код только в памяти запущенного процесса, наша модификация продержится только до завершения процесса.
Бонус — декомпилированная версия функции CTaskGroup::DoesWindowMatch
Несмотря на то, что оригинальный язык данного кода — C++, ниже превидена самописная декомпиляция на чистом C.
/*
return:
S_OK if there's a match, E_FAIL otherwise
*pnMatch:
1: AppId matches, ITEMIDLIST is missing
2: AppId matches, ITEMIDLIST doesn't match
3: ITEMIDLIST matches
4: hWnd already exists (pTaskItem recieves the CTaskItem)
*/
HRESULT CTaskGroup_DoesWindowMatch(void /*CTaskGroup*/ *this,
/*IN*/ HWND hCompareWnd, /*IN*/ ITEMIDLIST *pCompareItemIdList, /*IN*/ WCHAR *pCompareAppId,
/*OUT*/ int *pnMatch, /*OUT*/ void /*CTaskItem*/ **pTaskItem)
{
void /*CTaskItem*/ *CompareWndTaskItem;
int nMatch;
HRESULT hr;
nMatch = 0;
hr = this->lpVtbl->GetItemFromWindow(this, hCompareWnd, &CompareWndTaskItem);
if(SUCCEEDED(hr)) // if an item for this window already exists
{
if(pTaskItem)
{
*pTaskItem = NULL;
IUnknown_Set(pTaskItem, CompareWndTaskItem);
}
nMatch = 4;
CompareWndTaskItem->lpVtbl->Release(CompareWndTaskItem);
}
else
{
if(!(this->SomeFlags & 0x80000000))
{
if(pCompareItemIdList && this->pItemIdList && ILIsEqual(pCompareItemIdList, this->pItemIdList) != 0)
{
nMatch = 3;
hr = S_OK;
}
else if(pCompareAppId && CompareStringOrdinal(this->pAppId, -1, pCompareAppId, -1, TRUE) == CSTR_EQUAL)
{
hr = S_OK;
if(pCompareItemIdList && this->pItemIdList)
nMatch = 2;
else
nMatch = 1;
}
}
}
*pnMatch = nMatch;
return hr;
}
Готовое решение
Программа 7+ Taskbar Tweaker, автором которой являюсь я, умеет отключать группировку как глобально, так и выборочно по Application ID. Кроме этого, имеются в наличие еще несколько интересных настроек панели задач.
Заключение
Иногда я открываю статью на хабре, которая кажется мне интересной, но после того как начинаю читать, понимаю что она не такая уж и интересная (или не очень понятная). В таких случаях я часто сразу перелистываю вниз к заключению/выводам.
Если вы находитесь здесь по этой же причине, расскажу вкратце о чем шла речь:
Я взял отладчик, и присоединил его к проводнику. Затем я нашел нужную функцию, и изменил ее так, чтобы проводник думал, что любое созданное окошко не подходит ни к одной из существующих групп. В связи с этим изменением, проводник перестал группировать кнопки на панели задач.