1

Тема: AHK: Установка хуков на события мыши и клавиатуры

Хук — это механизм, с помощью которого приложения могут перехватывать события — сообщения, действия мыши и нажатия клавиш. Функция, которая перехватывает определённые типы событий, называется хук-процедура (hook procedure). Хук-процедура может производить различные действия над каждым получаемым событием, модифицировать или исключать его. MSDN пишет, что если приложение устанавливает хук для отслеживания событий другого приложения, хук-процедура должна находиться в отдельной dll.

Однако замечено, что для отслеживания событий мыши и клавиатуры можно обойтись без написания dll и расположить хук-процедуру непосредственно в теле скрипта.

Клавиатурный хук:

OnExit, Exit
hHookKeybd := DllCall("SetWindowsHookEx"
   , Int, WH_KEYBOARD_LL := 13
   , Ptr, RegisterCallback("LowLevelKeyboardProc", "Fast")
   , Ptr, DllCall("GetModuleHandle", UInt, 0, Ptr)
   , UInt, 0, Ptr)
Return

Exit:
   DllCall("UnhookWindowsHookEx", Ptr, hHookKeybd)
   ExitApp

LowLevelKeyboardProc(nCode, wParam, lParam)
{
   static msg, vk, sc, time
      , oMsg := {0x100: "WM_KEYDOWN", 0x101: "WM_KEYUP", 0x104: "WM_SYSKEYDOWN", 0x105: "WM_SYSKEYUP"}
   
   SetFormat, IntegerFast, H
   msg := wParam . ""                       ; добавляем "", чтобы шестнадцатеричное число интерпретировалось, 
   vk  := NumGet(lParam + 0, "UInt") . ""   ; как строка, и не превратилось в десятичное после смены формата
   ext := NumGet(lParam + 8, "UInt") & 1
   sc  := NumGet(lParam + 4, "UInt") | ext << 8 . ""
   
   SetFormat, IntegerFast, D
   time := NumGet(lParam + 12, "UInt")
   
   SetTimer, EventHandling, -10   ; обработку событий "повесим" на таймер, чтобы не создавать помех в работе клавиатуры
   Return DllCall("CallNextHookEx", Ptr, 0, Int, nCode, Ptr, wParam, Ptr, lParam)
   
EventHandling:
   TrayTip,, % "Message = "  . oMsg[msg]
             . "`nvkCode = " . vk
             . "`nscCode = " . sc
             . "`nTime = "   . time
   Return
}

Esc::ExitApp

Хук на события мыши:

OnExit, Exit
hHookMouse := DllCall("SetWindowsHookEx"
   , Int, WH_MOUSE_LL := 14
   , Ptr, RegisterCallback("LowLevelMouseProc", "Fast")
   , Ptr, DllCall("GetModuleHandle", UInt, 0, Ptr)
   , UInt, 0, Ptr)
 Return

Exit:
    DllCall("UnhookWindowsHookEx", Ptr, hHookMouse)
    ExitApp

LowLevelMouseProc(nCode, wParam, lParam)
{
   static msg, mouse_x, mouse_y, ext, time
      , oMsg := { 0x200: "WM_MOUSEMOVE"
                , 0x201: "WM_LBUTTONDOWN", 0x202: "WM_LBUTTONUP"
                , 0x204: "WM_RBUTTONDOWN", 0x205: "WM_RBUTTONUP"
                , 0x207: "WM_MBUTTONDOWN", 0x208: "WM_MBUTTONUP"
                , 0x20B: "WM_XBUTTONDOWN", 0x20C: "WM_XBUTTONUP"
                , 0x20A: "WM_MOUSEWHEEL" , 0x20E: "WM_MOUSEHWHEEL" }
                
      , oMouseData := { 0: "0", 1: "XBUTTON1", 2: "XBUTTON2"
                      , 120: "WHEEL_DELTA forward", -120: "WHEEL_DELTA backward" }

   msg := wParam
   mouse_x := NumGet(lParam + 0, "Int")
   mouse_y := NumGet(lParam + 4, "Int")
   ext     := NumGet(lParam + 10, "Short")
   time    := NumGet(lParam + 16, "UInt")
   
   SetTimer, EventHandling, -10
   Return DllCall("CallNextHookEx", Ptr, 0, Int, nCode, UInt, wParam, UInt, lParam)

EventHandling:
   ToolTip, % "Message = "     . oMsg[msg]
            . "`nMouse_X = "   . mouse_x
            . "`nMouse_Y = "   . mouse_y
            . "`nMouseData = " . oMouseData[ext]
            . "`nTime = "      . time
   Return
}

Esc::ExitApp

В случае, если параметр nCode хук-процедуры меньше нуля, она обязательно должна возвращать значение, возвращаемое функцией CallNextHookEx(). В остальных случаях также возвращение данного значения желательно, иначе другие приложения, где установлен такой же хук, не будут получать событий, что может привести к некорректной их работе.

Примеры использования:
Полная блокировка событий мыши
Инверсия мыши по вертикали
Определение символов, посылаемых с клавиатуры
Определение виртуальных и скан-кодов клавиатуры

Информация:
SetWindowsHookEx()
LowLevelKeyboardProc()
LowLevelMouseProc()

Тема для обсуждения.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

2

Re: AHK: Установка хуков на события мыши и клавиатуры

Обновлённый код для клавиатурного хука. Добавлена технология, предложенная коллегой serzh82saratov, для сохранения и обработки всех событий без потерь в случае поступления их большого количества в короткий промежуток времени.

CoordMode, ToolTip
hHookKeybd := DllCall("SetWindowsHookEx"
   , Int, WH_KEYBOARD_LL := 13
   , Ptr, RegisterCallback("LowLevelKeyboardProc", "Fast")
   , Ptr, DllCall("GetModuleHandle", UInt, 0, Ptr)
   , UInt, 0, Ptr)
OnExit( Func("Exit").Bind(hHookKeybd) )
Return

Esc::ExitApp

Exit(hHookKeybd)  {
   DllCall("UnhookWindowsHookEx", Ptr, hHookKeybd)
}

LowLevelKeyboardProc(nCode, wParam, lParam)  {
   static oMem := [], lpData, size := VarSetCapacity(lpData, 16, 0)

   DllCall("RtlMoveMemory", Ptr, &lpData, Ptr, lParam, Ptr, size)
   oMem.Push([wParam, &lpData])
   timer := Func("EventHandling").Bind(oMem)  ; во избежание фризов клавиатуры обработка событий должна
   SetTimer, % timer, -10                     ; происходить обязательно по таймеру, а не в теле этой функции
   Return DllCall("CallNextHookEx", Ptr, 0, Int, nCode, Ptr, wParam, Ptr, lParam)
}

EventHandling(oMem)  {
   static oMsg := {0x100: "WM_KEYDOWN", 0x101: "WM_KEYUP", 0x104: "WM_SYSKEYDOWN", 0x105: "WM_SYSKEYUP"}
        , oMembers := ["vk", "sc", "flags", "time"]
        , oFlagOffsets := {LLKHF_EXTENDED: 0, LLKHF_LOWER_IL_INJECTED: 1, LLKHF_INJECTED: 4, LLKHF_ALTDOWN: 5, LLKHF_UP: 7}

   while (oMem[1] != "")   {
      key := oMem.RemoveAt(1)
      wp := key[1], lp := key[2]

      msg := wp
      for k, v in oMembers
         %v% := NumGet(lp + (k - 1)*4, "UInt")
      
      for k, v in oFlagOffsets
         %k% := (flags >> v) & 1
      
      sc |= LLKHF_EXTENDED << 8
      
; текстовые значения в oMembers и названия ключей в oFlagOffsets являются
; названиями переменных, в которых теперь находятся соответствующие значения:
      ToolTip % "Message = "   . oMsg[msg]           . "`n"
              . "vkCode = "    . Format("{:#x}", vk) . "`n"
              . "scCode = "    . Format("{:#x}", sc) . "`n"
              . "pressed = "   . !LLKHF_UP           . "`n"
              . "Time = "      . time                . "`n"
              . "Injected = "  . LLKHF_INJECTED      . "`n"
              . "Lower_IL_Injected = " . LLKHF_LOWER_IL_INJECTED, 0, 0
   } 
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

3

Re: AHK: Установка хуков на события мыши и клавиатуры

Обновлённый хук на события мыши.

#Persistent
MouseHook.Set()
; чтобы удалить
; MouseHook.Del()

class MouseHook
{
   Set()  {
      if !this.hook  {
         this.hook := this.SetMouseHook()
         OnExit( this.OnExit := ObjBindMethod(this, "Del") )
      }
   }
   
   Del()  {
      if this.hook  {
         this.SetMouseHook(this.hook)
         this.hook := ""
         OnExit(this.OnExit, 0)
      }
   }
   
   SetMouseHook(hHook := "")  {
      if hHook
         DllCall("UnhookWindowsHookEx", Ptr, hHook)
      else
         Return hHook := DllCall("SetWindowsHookEx", Int, WH_MOUSE_LL := 14
                                                   , Ptr, RegisterCallback("LowLevelMouseProc", "Fast")
                                                   , Ptr, DllCall("GetModuleHandle", UInt, 0, Ptr)
                                                   , UInt, 0, Ptr)
   }
}

LowLevelMouseProc(nCode, wParam, lParam)  {
   static oMem := [], lpData, size := VarSetCapacity(lpData, 16 + A_PtrSize*2, 0)

   DllCall("RtlMoveMemory", Ptr, &lpData, Ptr, lParam, Ptr, size)
   oMem.Push([wParam, &lpData])
   timer := Func("EventHandling").Bind(oMem)  ; во избежание фризов мыши обработка событий должна
   SetTimer, % timer, -10                     ; происходить обязательно по таймеру, а не в теле этой функции
   Return DllCall("CallNextHookEx", Ptr, 0, Int, nCode, Ptr, wParam, Ptr, lParam)
}

EventHandling(oMem)  {
   static oMsg := { 0x200: "WM_MOUSEMOVE"
                  , 0x201: "WM_LBUTTONDOWN", 0x202: "WM_LBUTTONUP"
                  , 0x204: "WM_RBUTTONDOWN", 0x205: "WM_RBUTTONUP"
                  , 0x207: "WM_MBUTTONDOWN", 0x208: "WM_MBUTTONUP"
                  , 0x20B: "WM_XBUTTONDOWN", 0x20C: "WM_XBUTTONUP"
                  , 0x20A: "WM_MOUSEWHEEL" , 0x20E: "WM_MOUSEHWHEEL" }
                
        , oMouseData := { 0: "0", 1: "XBUTTON1", 2: "XBUTTON2"
                        , 120: "WHEEL_DELTA forward", -120: "WHEEL_DELTA backward" }
                        
   prevTTCoordMode := A_CoordModeToolTip
   CoordMode, ToolTip
   while event := oMem.RemoveAt(1)  {
      msg       := oMsg[ event[1] ]
      mouse_x   := NumGet(event[2] + 0, "Int")
      mouse_y   := NumGet(event[2] + 4, "Int")
      ext       := NumGet(event[2] + 10, "Short")
      flags     := NumGet(event[2] + 12, "UInt")
      time      := NumGet(event[2] + 16, "UInt")
      INJECTED  := flags & 1
      LOWER_IL_INJECTED := flags >> 1
      mouseData := oMouseData[ext]
      
      ToolTip % "Msg: "  . A_Tab . A_Tab . msg               . "`n"
              . "MouseData: "    . A_Tab . mouseData         . "`n"
              . "MouseX: "       . A_Tab . mouse_x           . "`n"
              . "MouseY: "       . A_Tab . mouse_y           . "`n"
              . "INJECTED: "     . A_Tab . INJECTED          . "`n"
              . "LIL_INJECTED: " . A_Tab . LOWER_IL_INJECTED . "`n"
              . "Time: " . A_Tab . A_Tab . time, 0, 0, 20
   }
   CoordMode, ToolTip, % prevTTCoordMode
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder