Blog. Just Blog

Обработка колесика мыши над иконкой в трее

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
"По следам наших публикаций", как любили писать в советской прессе. В предыдущей статье я обещал рассказать, каким образом можно реализовать обработку вращения колесика мыши над иконкой в системном трее. Поскольку система не отправляет иконкам в трее сообщение WM_MOUSEWHEEL, обрабатывать его мы будем при помощи хуков и описанных в предыдущей статье методов определения, что курсор находится над нужной иконкой. Начнем с локального хука. Установка и снятие хука выполняется самым обычным способом, например:
  1. proc DialogProc hwnddlg,msg,wparam,lparam
  2.         push    ebx esi edi
  3.         cmp     [msg],WM_INITDIALOG
  4.         je      .wminitdialog
  5.         cmp     [msg],WM_CLOSE
  6.         je      .wmclose
  7.         ...
  8.         ...
  9.         xor     eax,eax
  10.         jmp     .finish
  11.  
  12. .wminitdialog:
  13.         ...
  14.         ...
  15.         ; Показать иконку в трее
  16.         invoke  Shell_NotifyIcon, NIM_ADD,node
  17.  
  18.         ; Установить обработчик мыши
  19.         invoke  GetCurrentThreadId
  20.         invoke  SetWindowsHookEx,WH_MOUSE,MousewheelProc,NULL,eax
  21.         mov     [hhkm],eax
  22.  
  23.         mov     eax,[hwnddlg]
  24.         mov     [hwmain],eax
  25.         jmp     .processed
  26.  
  27.         ...
  28.         ...
  29. .wmclose:
  30.         ; Снять обработчик мыши
  31.         invoke  UnhookWindowsHookEx,[hhkm]
  32.         ; Удалить иконку из трея
  33.         invoke  Shell_NotifyIcon,NIM_DELETE,node
  34.  
  35.         ; Закрыть окно
  36.         invoke  EndDialog,[hwnddlg],0
  37. .processed:
  38.         mov     eax,1
  39. .finish:
  40.         pop     edi esi ebx
  41.         ret
  42. endp
Обработчик событий мыши проверяет, что пришло сообщение WM_MOUSEWHEEL и что курсор в текущий момент находится над нужной иконкой в трее. Если эти условия выполнены, то проверяется направление вращения колесика и, в зависимости от этого, выполняются те или иные действия. Имейте в виду, что для сообщения WM_MOUSEWHEEL в обработчик хука приходит структура не MOUSEHOOKSTRUCT, а MOUSEHOOKSTRUCTEX с дополнительным полем mouseData.
  1. ; Структура для обработчика хука
  2. struct MOUSEHOOKSTRUCTEX
  3.         pt           POINT
  4.         hwnd         dd ?
  5.         wHitTestCode dd ?
  6.         dwExtraInfo  dd ?
  7.         mouseData    dd ?
  8. ends
  9.  
  10. ;-------------------------------------------------------------
  11. ; Обработка mousewheel
  12. ;-------------------------------------------------------------
  13. proc MousewheelProc nCode:dword,wParam:dword,lParam:dword
  14.         pusha
  15.  
  16.         cmp     [nCode],0
  17.         jl      .loc_ret
  18.  
  19.         ; Это сообщение от колеса мыши?
  20.         cmp     [wParam],WM_MOUSEWHEEL
  21.         jne     .loc_ret
  22.  
  23.         ; Заполнить структуру для идентификации иконки
  24.         mov     [notify.cbSize],sizeof.NOTIFYICONIDENTIFIER
  25.         mov     eax,[hwmain]
  26.         mov     [notify.hWnd],eax
  27.         mov     [notify.uID],ICON_ID
  28.  
  29.         ; Получить координаты иконки в трее
  30.         invoke  Shell_NotifyIconGetRect,notify,rc
  31.  
  32.         ; Указатель на MOUSEHOOKSTRUCT
  33.         mov     ebx,[lParam]
  34.  
  35.         ; Курсор находится внутри прямоугольника иконки?
  36.         invoke  PtInRect,rc,[ebx+MOUSEHOOKSTRUCTEX.pt.x],\
  37.                 [ebx+MOUSEHOOKSTRUCTEX.pt.y]
  38.         or      eax,eax
  39.         ; Нет, ничего не делать
  40.         jz      .loc_ret
  41.  
  42.         ; Проверить направление поворота колесика
  43.         mov     eax,[ebx+MOUSEHOOKSTRUCTEX.mouseData]
  44.         or      eax,eax
  45.         js      .loc_down
  46. .loc_up:
  47.         ; Вращение вверх
  48.         ...
  49.         ...
  50.         jmp     .loc_ret
  51. .loc_down:
  52.         ; Вращение вниз
  53.         ...
  54.         ...
  55.  
  56. .loc_ret:
  57.         popa
  58.         invoke  CallNextHookEx,[hhkm],[nCode],[wParam],[lParam]
  59.         ret
  60. endp
Способ замечательно работает, но есть одно большое "НО". Локальный хук будет срабатывать только в том случае, когда активно окно приложения. Если ваше приложение подразумевает именно такой принцип работы, то все нормально, больше ничего придумывать не надо.

Для более правильной реализации обработки колесика мыши воспользуемся глобальным хуком на низкоуровневые события мыши, то есть WH_MOUSE_LL. Небольшие отличия будут только в установке обработчика и в структуре, приходящей в обработчик хука.
  1. proc DialogProc hwnddlg,msg,wparam,lparam
  2.         push    ebx esi edi
  3.         cmp     [msg],WM_INITDIALOG
  4.         je      .wminitdialog
  5.         cmp     [msg],WM_CLOSE
  6.         je      .wmclose
  7.         ...
  8.         ...
  9.         xor     eax,eax
  10.         jmp     .finish
  11.  
  12. .wminitdialog:
  13.         ...
  14.         ...
  15.         ; Показать иконку в трее
  16.         invoke  Shell_NotifyIcon, NIM_ADD,node
  17.  
  18.         ; Установить обработчик низкоуровневых событий мыши
  19.         invoke  SetWindowsHookEx,WH_MOUSE_LL,MousewheelProc,NULL,NULL
  20.         mov     [hhkm],eax
  21.  
  22.         mov     eax,[hwnddlg]
  23.         mov     [hwmain],eax
  24.         jmp     .processed
  25.  
  26.         ...
  27.         ...
  28. .wmclose:
  29.         ; Снять обработчик мыши
  30.         invoke  UnhookWindowsHookEx,[hhkm]
  31.         ; Удалить иконку из трея
  32.         invoke  Shell_NotifyIcon,NIM_DELETE,node
  33.  
  34.         ; Закрыть окно
  35.         invoke  EndDialog,[hwnddlg],0
  36. .processed:
  37.         mov     eax,1
  38. .finish:
  39.         pop     edi esi ebx
  40.         ret
  41. endp
В обработчике хука поменялся адрес данных поворота колесика, так как используется структура MSLLHOOKSTRUCT. Тут поле mouseData сразу на месте, так что можно адресоваться по нему.
  1. ; Структура для обработчика хука
  2. struct MSLLHOOKSTRUCT
  3.         pt          POINT
  4.         mouseData   dd ?
  5.         flags       dd ?
  6.         time        dd ?
  7.         dwExtraInfo dd ?
  8. ends
  9.  
  10. ;-------------------------------------------------------------
  11. ; Обработка mousewheel
  12. ;-------------------------------------------------------------
  13. proc MousewheelProc nCode:dword,wParam:dword,lParam:dword
  14.         pusha
  15.  
  16.         cmp     [nCode],0
  17.         jl      .loc_ret
  18.  
  19.         ; Это сообщение от колеса мыши?
  20.         cmp     [wParam],WM_MOUSEWHEEL
  21.         jne     .loc_ret
  22.  
  23.         ; Заполнить структуру для идентификации иконки
  24.         mov     [notify.cbSize],sizeof.NOTIFYICONIDENTIFIER
  25.         mov     eax,[hwmain]
  26.         mov     [notify.hWnd],eax
  27.         mov     [notify.uID],ICON_ID
  28.  
  29.         ; Получить координаты иконки в трее
  30.         invoke  Shell_NotifyIconGetRect,notify,rc
  31.  
  32.         ; Указатель на MSLLHOOKSTRUCT
  33.         mov     ebx,[lParam]
  34.  
  35.         ; Курсор находится внутри прямоугольника иконки?
  36.         invoke  PtInRect,rc,[ebx+MSLLHOOKSTRUCT.pt.x],[ebx+MSLLHOOKSTRUCT.pt.y]
  37.         or      eax,eax
  38.         ; Нет, ничего не делать
  39.         jz      .loc_ret
  40.  
  41.         ; Проверить направление поворота колесика
  42.         mov     eax,[ebx+MSLLHOOKSTRUCT.mouseData]
  43.         or      eax,eax
  44.         js      .loc_down
  45. .loc_up:
  46.         ; Вращение вверх
  47.         ...
  48.         ...
  49.         jmp     .loc_ret
  50. .loc_down:
  51.         ; Вращение вниз
  52.         ...
  53.         ...
  54.  
  55. .loc_ret:
  56.         popa
  57.         invoke  CallNextHookEx,[hhkm],[nCode],[wParam],[lParam]
  58.         ret
  59. endp
Теперь событие от поворота колесика мыши будет срабатывать всегда, даже если окно приложения сейчас не активно.

Есть еще один вариант, как можно установить глобальный хук - вынести его в DLL. Информацию о вращении колесика обработчик будет отправлять главному окну приложения через пользовательские сообщения. Обработка колесика, соответственно, переносится из хука в основное приложение.
  1. ; Пользовательское сообщение для главного окна
  2. WM_OUR_MOUSE = WM_USER + 702
  3.  
  4. ;-------------------------------------------------------------
  5. ; Инициализация DLL
  6. ;-------------------------------------------------------------
  7. proc DllEntryPoint hinstDLL:dword,fdwReason:dword,lpvReserved:dword
  8.         mov     eax,[hinstDLL]
  9.         mov     [dll_base],eax
  10.         mov     eax,TRUE
  11.         ret
  12. endp
  13.  
  14. ;-------------------------------------------------------------
  15. ; Установка хука mousewheel
  16. ;-------------------------------------------------------------
  17. proc    InitMousewheelHook parent_hwnd:DWORD
  18.         pusha
  19.         cmp     [hhkm],0
  20.         jne     @f
  21.         invoke  SetWindowsHookEx,WH_MOUSE,MousewheelProc,[dll_base],NULL
  22.         mov     [hhkm],eax
  23.         mov     eax,[parent_hwnd]
  24.         mov     [hhwnd],eax
  25. @@:
  26.         popa
  27.         ret
  28. endp
  29.  
  30. ;-------------------------------------------------------------
  31. ; Снятие хука mousewheel
  32. ;-------------------------------------------------------------
  33. proc    KillMousewheelHook
  34.         pusha
  35.         cmp     [hhkm],0
  36.         je      @f
  37.         invoke  UnhookWindowsHookEx,[hhkm]
  38.         mov     [hhkm],NULL
  39. @@:
  40.         popa
  41.         ret
  42. endp
  43.  
  44. ;-------------------------------------------------------------
  45. ; Обработка mousewheel
  46. ;-------------------------------------------------------------
  47. proc MousewheelProc nCode:dword,wParam:dword,lParam:dword
  48.         pusha
  49.  
  50.         cmp     [nCode],0
  51.         jl      .loc_ret
  52.  
  53.         ; Это сообщение от колеса мыши?
  54.         cmp     [wParam],WM_MOUSEWHEEL
  55.         jne     .loc_ret
  56.  
  57.         ; Получить направление вращения колесика
  58.         mov     eax,[lParam]
  59.         mov     eax,[eax+20]
  60.  
  61.         ; Отправить сообщение главному окну программы
  62.         invoke  PostMessage,[hhwnd],WM_OUR_MOUSE,1,eax
  63.  
  64. .loc_ret:
  65.         popa
  66.         invoke  CallNextHookEx,[hhkm],[nCode],[wParam],[lParam]
  67.         ret
  68. endp
В обработчике окна добавляется проверка пользовательского сообщения, которое будет приходить из копий DLL, загруженных в адресное пространство других процессов. Также добавляется установка и снятие глобального хука.
  1. proc DialogProc hwnddlg,msg,wparam,lparam
  2.         push    ebx esi edi
  3.         cmp     [msg],WM_INITDIALOG
  4.         je      .wminitdialog
  5.         cmp     [msg],WM_CLOSE
  6.         je      .wmclose
  7.         ; Это пользовательское сообщение от DLL?
  8.         cmp     [msg],WM_OUR_MOUSE
  9.         je      .get_coord
  10.         ...
  11.         ...
  12.         xor     eax,eax
  13.         jmp     .finish
  14.  
  15. .wminitdialog:
  16.         ...
  17.         ...
  18.         ; Показать иконку в трее
  19.         invoke  Shell_NotifyIcon, NIM_ADD,node
  20.  
  21.         ; Установить глобальный обработчик мыши
  22.         invoke  InitMousewheelHook,[hwnddlg]
  23.  
  24.         jmp     .processed
  25.  
  26.         ...
  27.         ...
  28. .get_coord:
  29.         ; Заполнить структуру для идентификации иконки
  30.         mov     [notify.cbSize],sizeof.NOTIFYICONIDENTIFIER
  31.         mov     eax,[hwnddlg]
  32.         mov     [notify.hWnd],eax
  33.         mov     [notify.uID],ICON_ID
  34.  
  35.         ; Получить координаты иконки в трее
  36.         invoke  Shell_NotifyIconGetRect,notify,rc
  37.  
  38.         ; Получить позицию курсора
  39.         invoke  GetCursorPos,pt
  40.         ; Курсор находится внутри прямоугольника иконки?
  41.         invoke  PtInRect,rc,[pt.x],[pt.y]
  42.         or      eax,eax
  43.         ; Нет, ничего не делать
  44.         jz      .processed
  45.  
  46.         ; Проверить направление поворота колесика
  47.         mov     eax,[lparam]
  48.         or      eax,eax
  49.         js      @f
  50. .loc_up:
  51.         ; Вращение вверх
  52.         ...
  53.         ...
  54.         jmp     .processed
  55. .loc_down:
  56.         ; Вращение вниз
  57.         ...
  58.         ...
  59.         jmp     .processed
  60.  
  61. .wmclose:
  62.         ; Снять глобальный обработчик мыши
  63.         invoke  KillMousewheelHook
  64.         ; Удалить иконку из трея
  65.         invoke  Shell_NotifyIcon,NIM_DELETE,node
  66.  
  67.         ; Закрыть окно
  68.         invoke  EndDialog,[hwnddlg],0
  69. .processed:
  70.         mov     eax,1
  71. .finish:
  72.         pop     edi esi ebx
  73.         ret
  74. endp
Как и в варианте с низкоуровневыми событиями мыши, колесико будет обрабатываться вне зависимости от состояния окна. Не забываем про отдельные DLL для процессов разных разрядностей.

В приложении примеры программ с исходными текстами, которые обрабатывают вращение колесика мыши над иконкой в трее при помощи локального и глобального хуков.

Примеры программ с исходными текстами (FASM)Примеры программ с исходными текстами (FASM)

Tray.Icon.Mousewheel.Demo.zip (15,244 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 889 | Комментариев: 8

Метки: Assembler, иконки
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Гость (10.01.2023 в 21:26):
В реестре есть настройка SetWindowsHookEx ключом LowLevelHooksTimeout, возможно будет полезен.
ManHunter (12.11.2021 в 18:18):
Немного причесал код для наглядности. Архив обновлен.
SMaSm-94 (12.11.2021 в 17:21):
ManHunter, упс. Ошибся - бывает. Извиняюсь)
ManHunter (12.11.2021 в 17:12):
SMaSm-94, нет, там же приходит не просто сообщение с lparam, а целая структура, в которой координаты события обозначены через POINT:
https://docs.microsoft.com/ru-...sehookstruct
https://docs.microsoft.com/ru-...llhookstruct
У DRON все написано правильно.
SMaSm-94 (12.11.2021 в 17:08):
@DRON, тогда уж вот так вот (не проверял код на работоспособность):
... ; код обработки сообщения WM_MOUSEWHEEL
mov eax,[lParam]
movzx ecx,ax ; LOWORD - координата курсора по оси X (относительно левого верхнего угла экрана)
shr eax,16 ; HIWORD - координата курсора по оси Y (относительно левого верхнего угла экрана)
invoke PtInRect,rc,ecx,eax
...
DRON (12.11.2021 в 16:46):
Просто я когда-то уже решал аналогичную задачу при написании плагина к фубару: https://foobar2000.ru/forum/vi...c.php?t=2177

Ни на что не влияет, но во всех хуках координаты приходят в параметрах, поэтому можно вместо GetCursorPos делать что-то вроде:
        mov     eax,[lParam]
        invoke  PtInRect,rc,[eax],[eax+4]
ManHunter (12.11.2021 в 15:55):
Добавил пример с WH_MOUSE_LL. Архив обновлен.
DRON, чувствую, пора бежать за пивом, поляну накрывать :)
DRON (12.11.2021 в 15:37):
Не надо никаких DLL-ок: WM_MOUSEHWHEEL прекрасно ловится из WH_MOUSE_LL хука.

Вообще, для данной конкретной задачи определять координаты иконки (это я про первую статью) не требуется: можно ловить обычный WM_MOUSEMOVE в обработчике иконки, сравнивать его с координатами из хука и если они не совпадают, то значит курсор вышел за пределы иконки.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.08 сек. / MySQL: 2 (0.0051 сек.) / Память: 4.5 Mb
Наверх