Правильное принудительное обновление иконок в трее
Несколько лет назад я опубликовал в блоге статью о принудительном обновлении иконок в трее, в которой опрометчиво заявил, что это единственный способ решения задачи. Все течет, все изменяется, поэтому представляю вашему вниманию новую статью-опровержение, в которой расскажу, как правильно обновлять иконки в трее.Суть задачи остается прежней: некое приложение аварийно завершило работу, в трее остался его значок. Нужно из своего приложения обновить системный трей таким образом, чтобы этот значок убрать. Все трудности этой задачи я подробно описал в статье по ссылке выше, поэтому повторяться не буду и перейду сразу к решению. За основу взят код получения списка иконок в трее. При переборе проверяется наличие окна, которому должна принадлежать иконка. Если окна нет, значит и иконки существовать не должно. Такие иконки удаляются при помощи функции Shell_NotifyIcon с параметром NIM_DELETE.
Code (Assembler) : Убрать нумерацию
- ; Сегмент данных
- section '.data' data readable writeable
- ; Структура пользовательских данных иконки
- struct EXTRADATA
- Wnd dd ?
- uID dd ?
- ends
- class1 db 'Shell_TrayWnd',0 ; Название класса окна трея
- class2 db 'TrayNotifyWnd',0 ; Название класса панели уведомлений
- class3 db 'SysPager',0 ; Трей
- class4 db 'ToolbarWindow32',0 ; Панель с иконками
- ; Структура для кнопки
- button TBBUTTON
- ; Структура для пользовательских данных иконки
- extra EXTRADATA
- ; Структура для работы с иконкой в трее
- nid NOTIFYICONDATA
- hToolbar dd ? ; Хэндл окна с иконками
- IconsCount dd ? ; Количество иконок в трее
- ProcId dd ? ; Id процесса
- hProcess dd ? ; Хэндл процесса
- lpData dd ? ; Указатель на блок памяти
- BytesRead dd ? ; Количество прочитанных символов
Code (Assembler) : Убрать нумерацию
- ; Найти окно трея
- invoke FindWindow,class1,NULL
- or eax,eax
- jz exit_process
- ; Найти панель уведомлений
- invoke FindWindowEx,eax,NULL,class2,NULL
- or eax,eax
- jz exit_process
- ; Найти трей
- invoke FindWindowEx,eax,NULL,class3,NULL
- or eax,eax
- jz exit_process
- ; Найти панель иконок в трее
- invoke FindWindowEx,eax,NULL,class4,NULL
- or eax,eax
- jz exit_process
- ; Сохранить хэндл окна с иконками
- mov [hToolbar],eax
- ; Получить количество иконок в трее
- invoke SendMessage,eax,TB_BUTTONCOUNT,0,0
- or eax,eax
- jz exit_process
- ; Сохранить количество иконок в трее
- mov [IconsCount],eax
- ; Получить ID процесса-владельца трея
- invoke GetWindowThreadProcessId,[hToolbar],ProcId
- ; Открыть процесс с для чтения и записи
- invoke OpenProcess,PROCESS_VM_OPERATION or PROCESS_VM_READ,\
- FALSE,[ProcId]
- or eax,eax
- ; Фокус не удался
- jz exit_process
- ; Сохранить хэндл процесса-владельца трея
- mov [hProcess],eax
- ; Выделить блок памяти в контексте процесса
- invoke VirtualAllocEx,[hProcess],NULL,dword sizeof.TBBUTTON,\
- MEM_COMMIT,PAGE_READWRITE
- or eax,eax
- jz exit_process
- ; Сохранить указатель на блок памяти
- mov [lpData],eax
- ; Перебрать все иконки в трее
- loc_loop:
- ; Все иконки обработали?
- cmp [IconsCount],0
- je clear_memory
- ; Следующая иконка
- dec [IconsCount]
- ; Получить иконку из трея с индексом IconsCount
- invoke SendMessage,[hToolbar],TB_GETBUTTON,[IconsCount],[lpData]
- ; Прочитать структуру иконки
- invoke ReadProcessMemory,[hProcess],[lpData],button,\
- dword sizeof.TBBUTTON,BytesRead
- or eax,eax
- jz clear_memory
- ; Прочиталась вся структура?
- cmp [BytesRead],sizeof.TBBUTTON
- jnz clear_memory
- ; Прочитать пользовательские данные иконки
- invoke ReadProcessMemory,[hProcess],[button.dwData],extra,\
- dword sizeof.EXTRADATA,BytesRead
- or eax,eax
- jz clear_memory
- ; Прочиталась вся структура?
- cmp [BytesRead],sizeof.EXTRADATA
- jnz clear_memory
- ; Это скрытая иконка?
- mov eax,[extra.uID]
- and eax,80000000h
- or eax,eax
- ; Да, пропустить
- jnz loc_loop
- ; Окно процесса существует?
- invoke IsWindow,[extra.Wnd]
- or eax,eax
- jnz loc_loop
- ; Удалить иконку, у которой нет родителя
- mov [nid.cbSize],sizeof.NOTIFYICONDATA
- mov eax,[extra.Wnd]
- mov [nid.hWnd],eax
- mov eax,[extra.uID]
- mov [nid.uID],eax
- invoke Shell_NotifyIcon,NIM_DELETE,nid
- jmp loc_loop
- clear_memory:
- ; Очистить память и ресурсы
- invoke VirtualFreeEx,[hProcess],[lpData],0,MEM_RELEASE
- invoke CloseHandle,[hProcess]
- exit_process:
Есть еще один способ удаления иконки без использования функции Shell_NotifyIcon. Основной код останется практически таким же. Изменения коснутся только следующих участков:
Code (Assembler) : Убрать нумерацию
- hTaskbar dd ? ; Хэндл окна трея
- ...
- ...
- ; Найти окно трея
- invoke FindWindow,class1,NULL
- or eax,eax
- jz exit_process
- mov [hTaskbar],eax
- ...
- ...
- ; Окно процесса существует?
- invoke IsWindow,[extra.Wnd]
- or eax,eax
- jnz loc_loop
- ; Удалить иконку, у которой нет родителя
- invoke SendMessage,[hToolbar],TB_DELETEBUTTON,[IconsCount],0
- invoke SendMessage,[hTaskbar],WM_WININICHANGE,0,0
- jmp loc_loop
Code (Assembler) : Убрать нумерацию
- struct MEMORY_BASIC_INFORMATION
- BaseAddress dd ?
- AllocationBase dd ?
- AllocationProtect dd ?
- RegionSize dd ?
- State dd ?
- Protect dd ?
- Type dd ?
- ends
- struct EXTRADATA
- Wnd dd ?
- uID dd ?
- ends
- struct TBBUTTON64
- iBitmap dd ?
- idCommand dd ?
- fsState db ?
- fsStyle db ?
- Reserved rb 6
- dwData dq ?
- iString dq ?
- ends
- class1 db 'Shell_TrayWnd',0 ; Название класса окна трея
- class2 db 'TrayNotifyWnd',0 ; Название класса панели уведомлений
- class3 db 'SysPager',0 ; Трей
- class4 db 'ToolbarWindow32',0 ; Панель с иконками
- ntdll db 'ntdll.dll',0
- nwmname db 'NtWow64ReadVirtualMemory64',0
- ; Структура для кнопки
- button TBBUTTON64
- ; Структура для пользовательских данных иконки
- extra EXTRADATA
- ; Структура для чтения памяти процесса
- mbi MEMORY_BASIC_INFORMATION
- hToolbar dd ? ; Хэндл окна с иконками
- hTaskbar dd ? ; Хэндл окна трея
- IconsCount dd ? ; Количество иконок в трее
- ProcId dd ? ; Id процесса
- hProcess dd ? ; Хэндл процесса
- lpData dd ? ; Указатель на блок памяти
- BytesRead dd ? ; Количество прочитанных символов
- dRead dd ? ; Адрес NtWow64ReadVirtualMemory64
Code (Assembler) : Убрать нумерацию
- ; Найти окно трея
- invoke FindWindow,class1,NULL
- or eax,eax
- jz exit_process
- mov [hTaskbar],eax
- ; Найти панель уведомлений
- invoke FindWindowEx,eax,NULL,class2,NULL
- or eax,eax
- jz exit_process
- ; Найти трей
- invoke FindWindowEx,eax,NULL,class3,NULL
- or eax,eax
- jz exit_process
- ; Найти панель иконок в трее
- invoke FindWindowEx,eax,NULL,class4,NULL
- or eax,eax
- jz exit_process
- ; Сохранить хэндл окна с иконками
- mov [hToolbar],eax
- ; Получить количество иконок в трее
- invoke SendMessage,eax,TB_BUTTONCOUNT,0,0
- or eax,eax
- jz exit_process
- ; Сохранить количество иконок в трее
- mov [IconsCount],eax
- ; Получить ID процесса-владельца трея
- invoke GetWindowThreadProcessId,[hToolbar],ProcId
- ; Открыть процесс для чтения и записи
- invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,[ProcId]
- or eax,eax
- ; Фокус не удался
- jz exit_process
- ; Сохранить хэндл процесса-владельца трея
- mov [hProcess],eax
- ; NtWow64ReadVirtualMemory64
- invoke GetModuleHandle,ntdll
- invoke GetProcAddress,eax,nwmname
- or eax,eax
- jz exit_process
- mov [dRead],eax
- ; Выделить блок памяти в контексте процесса
- xor ebx,ebx
- loc_allocate_memory:
- invoke VirtualQueryEx,[hProcess],ebx,mbi,\
- sizeof.MEMORY_BASIC_INFORMATION
- or eax,eax
- jz error_memory
- cmp [mbi.State],MEM_FREE
- jne @f
- invoke VirtualAllocEx,[hProcess],dword [mbi.BaseAddress],\
- dword sizeof.TBBUTTON64,MEM_COMMIT+MEM_RESERVE,PAGE_READWRITE
- or eax,eax
- jnz memory_ok
- @@:
- add ebx,[mbi.RegionSize]
- cmp ebx,0x7FFFFFFF
- jb loc_allocate_memory
- error_memory:
- jmp exit_process
- memory_ok:
- ; Сохранить указатель на блок памяти
- mov [lpData],eax
- ; Перебрать все иконки в трее
- loc_loop:
- ; Все иконки обработали?
- cmp [IconsCount],0
- je clear_memory
- ; Следующая иконка
- dec [IconsCount]
- ; Получить иконку из трея с индексом IconsCount
- invoke SendMessage,[hToolbar],TB_GETBUTTON,[IconsCount],[lpData]
- ; Прочитать структуру иконки
- invoke ReadProcessMemory,[hProcess],[lpData],button,\
- dword sizeof.TBBUTTON64,BytesRead
- or eax,eax
- jz clear_memory
- ; Прочиталась вся структура?
- cmp [BytesRead],sizeof.TBBUTTON64
- jne clear_memory
- ; Прочитать пользовательские данные иконки
- mov esi,[dRead]
- push 0
- push 0
- push sizeof.EXTRADATA
- push extra
- push dword [button.dwData+4]
- push dword [button.dwData]
- stdcall esi,[hProcess]
- or eax,eax
- jnz clear_memory
- ; Это скрытая иконка?
- mov eax,[extra.uID]
- and eax,80000000h
- or eax,eax
- ; Да, пропустить
- jnz loc_loop
- ; Окно процесса существует?
- invoke IsWindow,dword [extra.Wnd]
- or eax,eax
- jnz loc_loop
- ; Удалить иконку, у которой нет родителя
- invoke SendMessage,[hToolbar],TB_DELETEBUTTON,[IconsCount],0
- invoke SendMessage,[hTaskbar],WM_WININICHANGE,0,0
- jmp loc_loop
- clear_memory:
- ; Очистить память и ресурсы
- invoke VirtualFreeEx,[hProcess],[lpData],0,MEM_RELEASE
- invoke CloseHandle,[hProcess]
- exit_process:
На своих компьютерах я иногда наблюдаю ситуацию, когда приложение вроде бы создало свою иконку в трее, но при этом иконка не отображается. Пока мне не удалось выяснить, с чем это связано, но решение я нашел. Если приложение написано по всем правилам, то оно должно обрабатывать ситуацию, когда произошел сбой системы и требуется обновить иконку в трее. Для этого надо отправить широковещательное сообщение TaskbarCreated.
Code (Assembler) : Убрать нумерацию
- szTBC db 'TaskbarCreated',0
- ...
- ...
- ; Отправить всем окнам сообщение о необходимости
- ; обновить свои иконки в трее (кто поддерживает)
- HWND_BROADCAST = 0xFFFF
- invoke RegisterWindowMessage,szTBC
- invoke SendMessage,HWND_BROADCAST,eax,NULL,NULL
В приложении примеры программ, обновляющие иконки в трее всеми описаннымы в статье способами. Для тестов прилагается программа, которая устанавливает свою иконку в трей, и не удаляет ее после выхода.
Просмотров: 2775 | Комментариев: 9
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
Kyklish
(04.08.2021 в 10:58):
Аварийное завершение MPC-HC оставляет иконки встроенных LAV декодеров&сплиттера в трее.
Из моих наблюдений:
Shell_NotifyIcon с параметром NIM_DELETE - заметчательно работает.
SendMessage с параметром TB_DELETEBUTTON - убивает иконки конкретно LAV декодеров&сплиттера (при повторном запуске MPC-HC появляются пустые иконки без меню по правому щелчку мыши) до следущего входа в систему.
Из моих наблюдений:
Shell_NotifyIcon с параметром NIM_DELETE - заметчательно работает.
SendMessage с параметром TB_DELETEBUTTON - убивает иконки конкретно LAV декодеров&сплиттера (при повторном запуске MPC-HC появляются пустые иконки без меню по правому щелчку мыши) до следущего входа в систему.
ManHunter
(31.01.2021 в 00:34):
А что тут крестового? Обычный чистый Си, обычные WinAPI.
Ivan_Alone
(30.01.2021 в 22:28):
Если кому-то вдруг нужно, вот реализация того же на C++. Обновил так сказать свой предыдущий комментарий в предыдущей публикации по этой теме.
Автору - спасибо за решение, очень полезно!
А про прошлый комментарий - ну не знал я про новую статью, она не выдаётся в поиске раньше старой, да и ссылок не было =)
HWND finder = FindWindow(L"Shell_TrayWnd", NULL);
if (finder) {
finder = FindWindowEx(finder, NULL, L"TrayNotifyWnd", NULL);
if (finder) {
finder = FindWindowEx(finder, NULL, L"SysPager", NULL);
if (finder) {
finder = FindWindowEx(finder, NULL, L"ToolbarWindow32", NULL);
int IconsCount = SendMessage(finder, TB_BUTTONCOUNT, 0, 0);
if (IconsCount) {
DWORD ProcId;
GetWindowThreadProcessId(finder, &ProcId);
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ, FALSE, ProcId);
if (hProcess) {
LPVOID lpData = VirtualAllocEx(hProcess, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
if (lpData) {
for (;;) {
if (IconsCount <= 0) {
break;
}
IconsCount--;
TBBUTTON button;
SIZE_T BytesRead;
SendMessage(finder, TB_GETBUTTON, IconsCount, (LPARAM)lpData);
if (ReadProcessMemory(hProcess, lpData, &button, sizeof(TBBUTTON), &BytesRead)) {
if (BytesRead != sizeof(TBBUTTON)) {
break;
}
EXTRADATA extra;
if (ReadProcessMemory(hProcess, (LPVOID)button.dwData, &extra, sizeof(EXTRADATA), &BytesRead)) {
if (BytesRead != sizeof(EXTRADATA)) {
break;
}
if (extra.uID & 0x80000000) {
continue;
}
if (IsWindow(extra.Wnd)) {
continue;
}
NOTIFYICONDATA nid;
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = extra.Wnd;
nid.uID = extra.uID;
// Способ 1
Shell_NotifyIcon(NIM_DELETE, &nid);
// Способ 2
/*SendMessage(finder, TB_DELETEBUTTON, IconsCount, 0);
SendMessage(finder, WM_WININICHANGE, 0, 0);*/
}
} else {
break;
}
}
VirtualFreeEx(hProcess, lpData, 0, MEM_RELEASE);
CloseHandle(hProcess);
}
}
}
}
}
}
Автору - спасибо за решение, очень полезно!
А про прошлый комментарий - ну не знал я про новую статью, она не выдаётся в поиске раньше старой, да и ссылок не было =)
HWND finder = FindWindow(L"Shell_TrayWnd", NULL);
if (finder) {
finder = FindWindowEx(finder, NULL, L"TrayNotifyWnd", NULL);
if (finder) {
finder = FindWindowEx(finder, NULL, L"SysPager", NULL);
if (finder) {
finder = FindWindowEx(finder, NULL, L"ToolbarWindow32", NULL);
int IconsCount = SendMessage(finder, TB_BUTTONCOUNT, 0, 0);
if (IconsCount) {
DWORD ProcId;
GetWindowThreadProcessId(finder, &ProcId);
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ, FALSE, ProcId);
if (hProcess) {
LPVOID lpData = VirtualAllocEx(hProcess, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
if (lpData) {
for (;;) {
if (IconsCount <= 0) {
break;
}
IconsCount--;
TBBUTTON button;
SIZE_T BytesRead;
SendMessage(finder, TB_GETBUTTON, IconsCount, (LPARAM)lpData);
if (ReadProcessMemory(hProcess, lpData, &button, sizeof(TBBUTTON), &BytesRead)) {
if (BytesRead != sizeof(TBBUTTON)) {
break;
}
EXTRADATA extra;
if (ReadProcessMemory(hProcess, (LPVOID)button.dwData, &extra, sizeof(EXTRADATA), &BytesRead)) {
if (BytesRead != sizeof(EXTRADATA)) {
break;
}
if (extra.uID & 0x80000000) {
continue;
}
if (IsWindow(extra.Wnd)) {
continue;
}
NOTIFYICONDATA nid;
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = extra.Wnd;
nid.uID = extra.uID;
// Способ 1
Shell_NotifyIcon(NIM_DELETE, &nid);
// Способ 2
/*SendMessage(finder, TB_DELETEBUTTON, IconsCount, 0);
SendMessage(finder, WM_WININICHANGE, 0, 0);*/
}
} else {
break;
}
}
VirtualFreeEx(hProcess, lpData, 0, MEM_RELEASE);
CloseHandle(hProcess);
}
}
}
}
}
}
Кулибин
(14.07.2017 в 03:07):
Большое спасибо!! :3
ManHunter
(05.07.2017 в 18:51):
Что мешает просто попробовать все варианты и выбрать нужный?
"Пастернака не читал, но осуждаю" :)
"Пастернака не читал, но осуждаю" :)
Doxtur
(05.07.2017 в 18:23):
По работе использую OPC шлюз, после потери связи не факт что он подключится, хотя будет писать что все ок. Приходилось перезагружать периодически, за пару месяцев оставалось просто гигантское количество иконок.
Я сделал немного по другому - сделал две версии одна для настроек с иконкой в трее, а во второй версии грубым битхаком вынес процедуру создания иконки.
Примеры не глядел - можно ли сделать консольку с параметрами - типа /1 первый способ, /2 - второй способ и т.п. ? Выберу годный и буду юзать и Вас вспоминать добрым словом)
Я сделал немного по другому - сделал две версии одна для настроек с иконкой в трее, а во второй версии грубым битхаком вынес процедуру создания иконки.
Примеры не глядел - можно ли сделать консольку с параметрами - типа /1 первый способ, /2 - второй способ и т.п. ? Выберу годный и буду юзать и Вас вспоминать добрым словом)
ManHunter
(30.06.2017 в 00:45):
После бессонной ночи в отладчике добавил вариант обновления иконок в трее из 32-битного приложения, запущенного на 64-битной системе. Очень рекомендую внимательно почитать. Архив обновлен.
ManHunter
(27.06.2017 в 14:05):
Добавил еще один вариант удаления мертвых иконок. Архив обновил.
Добавить комментарий
Заполните форму для добавления комментария
Только поменял
EXTRADATA extra;
на
struct EXTRADATA {
HWND Wnd;
UINT uID;
} extra{};
а то описание структуры где-то потерялось.
Кстати, вот и кресты, а не чистый си :)