Hi All,
The Microsoft Natural Keyboard 4000 has a "Zoom" slider in the centre of the keyboard that is kind of useless that I wanted to change to be a scroll slider. Unfortunately, it's not detected as a key by AutoHotkey normally. This is because the keyboard registers as a keyboard (which gets most of the input) AND a different HID device that receives input from the special keys.
Many other USB devices behave this way (e.g remote controls, mice with extended features). There have been a number of posts on this and Micha has posted a DLL to make this easier in AutoHotKey, but here's my stab at it.
First a script that lists all of the available raw input devices and can capture and decode all of the input. This is useful to work out what messages the device sends as well as the "Usage" and "Usage Page" of the top level collection of the HID device:
Code:
; List all of the "Raw Input" devices available for use and allow
; capture of output
;
; There may be more than one 'raw' device per device actually attached
; to the system. This is because these devices generally represent
; "HID Collections", and there may be more than one HID collection per
; USB device. For example, the Natural Keyboard 4000 supports a normal
; keyboard HID collection, plus an additional HID collection that can
; be used for the zoom slider and other important buttons
; Replace any previous instance
#SingleInstance force
DetectHiddenWindows, on
OnMessage(0x00FF, "InputMessage")
SizeofRawInputDeviceList := 8
SizeofRidDeviceInfo := 32
SizeofRawInputDevice := 12
RIM_TYPEMOUSE := 0
RIM_TYPEKEYBOARD := 1
RIM_TYPEHID := 2
RIDI_DEVICENAME := 0x20000007
RIDI_DEVICEINFO := 0x2000000b
RIDEV_INPUTSINK := 0x00000100
RID_INPUT := 0x10000003
DoCapture := 0
Gui, Add, Edit, HScroll w460 h300 vInfoOut -Wrap ReadOnly HwndInfoHwnd
Gui, Add, Edit, HScroll w460 h300 vEditOut -Wrap ReadOnly HwndEditHwnd
Gui, Add, Button, Default gCapture vCaptureButton w150, &Capture
Gui, Show, , HIDList
HWND := WinExist("HIDList")
Res := DllCall("GetRawInputDeviceList", UInt, 0, "UInt *", Count, UInt, SizeofRawInputDeviceList)
InfoOutput("There are " . Count . " raw input devices`r`n`r`n")
VarSetCapacity(RawInputList, SizeofRawInputDeviceList * Count)
Res := DllCall("GetRawInputDeviceList", UInt, &RawInputList, "UInt *", Count, UInt, SizeofRawInputDeviceList)
MouseRegistered := 0
KeyboardRegistered := 0
Loop %Count% {
Handle := NumGet(RawInputList, (A_Index - 1) * SizeofRawInputDeviceList)
Type := NumGet(RawInputList, (A_Index - 1) * SizeofRawInputDeviceList + 4)
if (Type = RIM_TYPEMOUSE)
TypeName := "RIM_TYPEMOUSE"
else if (Type = RIM_TYPEKEYBOARD)
TypeName := "RIM_TYPEKEYBOARD"
else if (Type = RIM_TYPEHID)
TypeName := "RIM_TYPEHID"
else
TypeName := "RIM_OTHER"
InfoOutput("Device " . A_Index . ": Handle " . Handle . " Type " . Type . " (" . TypeName . ") ")
Res := DllCall("GetRawInputDeviceInfo", UInt, Handle, UInt, RIDI_DEVICENAME, UInt, 0, "UInt *", Length)
VarSetCapacity(Name, Length + 2)
Res := DllCall("GetRawInputDeviceInfo", UInt, Handle, UInt, RIDI_DEVICENAME, "Str", Name, "UInt *", Length)
InfoOutput("Name " . Name . "`r`n")
VarSetCapacity(Info, SizeofRidDeviceInfo)
NumPut(SizeofRidDeviceInfo, Info, 0)
Length := SizeofRidDeviceInfo
Res := DllCall("GetRawInputDeviceInfo", UInt, Handle, UInt, RIDI_DEVICEINFO, UInt, &Info, "UInt *", SizeofRidDeviceInfo)
if (Type = RIM_TYPEMOUSE)
InfoOutput("Buttons " . NumGet(Info, 4 * 3) . " Sample rate " . NumGet(Info, 4 * 4) . "`r`n")
else if (Type = RIM_TYPEKEYBOARD)
InfoOutput("Mode " . NumGet(Info, 4 * 4) . " Function keys " . NumGet(Info, 4 * 5) . "`r`n")
else if (Type = RIM_TYPEHID)
{
InfoOutput("Vendor " . NumGet(Info, 4 * 2) . " Product " . NumGet(Info, 4 * 3) . " Version " . NumGet(Info, 4 * 4) . " ")
UsagePage := NumGet(Info, (4 * 5), "UShort")
Usage := NumGet(Info, (4 * 5) + 2, "UShort")
InfoOutput("Usage " . Usage . " Usage Page " . UsagePage . "`r`n")
}
; Keyboards are always Usage 6, Usage Page 1, Mice are Usage 2, Usage Page 1,
; HID devices specify their top level collection in the info block
VarSetCapacity(RawDevice, SizeofRawInputDevice)
NumPut(RIDEV_INPUTSINK, RawDevice, 4)
NumPut(HWND, RawDevice, 8)
DoRegister := 0
if (Type = RIM_TYPEMOUSE && MouseRegistered = 0)
{
DoRegister := 1
; Mice are Usage 2, Usage Page 1
NumPut(1, RawDevice, 0, "UShort")
NumPut(2, RawDevice, 2, "UShort")
MouseRegistered := 1
}
else if (Type = RIM_TYPEKEYBOARD && KeyboardRegistered = 0)
{
DoRegister := 1
; Keyboards are always Usage 6, Usage Page 1
NumPut(1, RawDevice, 0, "UShort")
NumPut(6, RawDevice, 2, "UShort")
KeyboardRegistered := 1
}
else if (Type = RIM_TYPEHID)
{
DoRegister := 1
NumPut(UsagePage, RawDevice, 0, "UShort")
NumPut(Usage, RawDevice, 2, "UShort")
}
if (DoRegister)
{
Res := DllCall("RegisterRawInputDevices", "UInt", &RawDevice, UInt, 1, UInt, SizeofRawInputDevice)
if (Res = 0)
InfoOutput("Failed to register for this device!`r`n")
else
InfoOutput("Registered for this device`r`n")
}
}
Count := 1
InputMessage(wParam, lParam, msg, hwnd)
{
global DoCapture
global RIM_TYPEMOUSE, RIM_TYPEKEYBOARD, RIM_TYPEHID
global RID_INPUT
if (DoCapture = 0)
return
Res := DllCall("GetRawInputData", UInt, lParam, UInt, RID_INPUT, UInt, 0, "UInt *", Size, UInt, 16)
VarSetCapacity(Buffer, Size)
Res := DllCall("GetRawInputData", UInt, lParam, UInt, RID_INPUT, UInt, &Buffer, "UInt *", Size, UInt, 16)
; AppendOutput(Mem2Hex(&Buffer, Size))
Type := NumGet(Buffer, 0 * 4)
Size := NumGet(Buffer, 1 * 4)
Handle := NumGet(Buffer, 2 * 4)
;AppendOutput("Got Input with " . Res . " size " . Size . " Type " . Type . " Handle " . Handle . "`r`n")
if (Type = RIM_TYPEMOUSE)
{
LastX := NumGet(Buffer, (16 + (4 * 3)), "Int")
LastY := NumGet(Buffer, (16 + (4 * 4)), "Int")
AppendOutput("HND " . Handle . " MOUSE LastX " . LastX . " LastY " . LastY . "`r`n")
}
else if (Type = RIM_TYPEKEYBOARD)
{
ScanCode := NumGet(Buffer, (16 + 0), "UShort")
VKey := NumGet(Buffer, (16 + 6), "UShort")
Message := NumGet(Buffer, (16 + 8))
AppendOutput("HND " . Handle . " KBD ScanCode " . ScanCode . " VKey " . VKey . " Msg " . Message . "`r`n")
}
else if (Type = RIM_TYPEHID)
{
SizeHid := NumGet(Buffer, (16 + 0))
InputCount := NumGet(Buffer, (16 + 4))
AppendOutput("HND " . Handle . " HID Size " . SizeHid . " Count " . InputCount . " Ptr " . &Buffer . "`r`n")
Loop %InputCount% {
Addr := &Buffer + 24 + ((A_Index - 1) * SizeHid)
BAddr := &Buffer
;MsgBox, BAddr %BAddr% Addr %Addr%
AppendOutput("Input " . Mem2Hex(Addr, SizeHid) . "`r`n")
}
}
else
{
AppendOutput("HND " . Handle . " Unknown Type " . Type)
}
return
}
return
Exit
Capture:
{
if (DoCapture = 0)
{
GuiControl, , CaptureButton, Stop &Capture
DoCapture := 1
}
else
{
GuiControl, , CaptureButton, &Capture
DoCapture := 0
}
return
}
GuiClose:
ExitApp
Mem2Hex( pointer, len )
{
A_FI := A_FormatInteger
SetFormat, Integer, Hex
Loop, %len% {
Hex := *Pointer+0
StringReplace, Hex, Hex, 0x, 0x0
StringRight Hex, Hex, 2
hexDump := hexDump . hex
Pointer ++
}
SetFormat, Integer, %A_FI%
StringUpper, hexDump, hexDump
Return hexDump
}
InfoOutput(Text)
{
global InfoHwnd
GuiControlGet, InfoOut
NewText := InfoOut . Text
GuiControl, , InfoOut, %NewText%
return
}
AppendOutput(Text)
{
global EditHwnd
GuiControlGet, EditOut
NewText := EditOut . Text
GuiControl, , EditOut, %NewText%
; WM_VSCROLL (0x115), SB_BOTTOM (7)
;MsgBox, %EditHwnd%
SendMessage, 0x115, 0x0000007, 0, , ahk_id %EditHwnd%
return
}
For example, the Natural keyboard 4000 shows up as:
Device 1: Handle 196713 Type 2 (RIM_TYPEHID) Name \??\HID#Vid_045e&Pid_00db&MI_01#8&34dbdc06&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
Vendor 1118 Product 219 Version 371 Usage 1 Usage Page 12
Here's a script that converts the zoom slider to be a scroll slider. It can easily be converted to accept input from any HID device:
Code:
; Capture input from the "Zoom" slider on the Natural Keyboard 4000
; and use it to scroll up and scroll down
; The HID top level collection for the Natural Keyboard 4000 is:
; Usage 1
; Usage Page 12
#NoTrayIcon
; Replace any previous instance
#SingleInstance force
DetectHiddenWindows, on
OnMessage(0x00FF, "InputMessage")
SizeofRawInputDeviceList := 8
SizeofRidDeviceInfo := 32
RIM_TYPEMOUSE := 0
RIM_TYPEKEYBOARD := 1
RIM_TYPEHID := 2
RIDI_DEVICENAME := 0x20000007
RIDI_DEVICEINFO := 0x2000000b
RIDEV_INPUTSINK := 0x00000100
RID_INPUT := 0x10000003
Usage := 1
UsagePage := 12
Gui, Show, Hide, NaturalCapture
HWND := WinExist("NaturalCapture")
; Keyboards are always Usage 6, Usage Page 1, Mice are Usage 2, Usage Page 1,
; HID devices specify their top level collection in the info block
VarSetCapacity(RawDevice, 12)
NumPut(RIDEV_INPUTSINK, RawDevice, 4)
NumPut(HWND, RawDevice, 8)
NumPut(UsagePage, RawDevice, 0, "UShort")
NumPut(Usage, RawDevice, 2, "UShort")
Res := DllCall("RegisterRawInputDevices", "UInt", &RawDevice, UInt, 1, UInt, 12)
if (Res = 0)
MsgBox, Failed to register for Natural Keyboard
InputMessage(wParam, lParam, msg, hwnd)
{
global DoCapture
global RIM_TYPEMOUSE, RIM_TYPEKEYBOARD, RIM_TYPEHID
global RID_INPUT
if (DoCapture = 0)
return
Res := DllCall("GetRawInputData", UInt, lParam, UInt, RID_INPUT, UInt, 0, "UInt *", Size, UInt, 16)
VarSetCapacity(Buffer, Size)
Res := DllCall("GetRawInputData", UInt, lParam, UInt, RID_INPUT, UInt, &Buffer, "UInt *", Size, UInt, 16)
Type := NumGet(Buffer, 0 * 4)
Size := NumGet(Buffer, 1 * 4)
Handle := NumGet(Buffer, 2 * 4)
;AppendOutput("Got Input with " . Res . " size " . Size . " Type " . Type . " Handle " . Handle . "`r`n")
if (Type = RIM_TYPEHID)
{
SizeHid := NumGet(Buffer, (16 + 0))
InputCount := NumGet(Buffer, (16 + 4))
;Debug("HND " . Handle . " HID Size " . SizeHid . " Count " . InputCount . " Ptr " . &Buffer . "`r`n")
Loop %InputCount% {
Addr := &Buffer + 24 + ((A_Index - 1) * SizeHid)
BAddr := &Buffer
;MsgBox, BAddr %BAddr% Addr %Addr%
Input := Mem2Hex(Addr, SizeHid)
;Debug("Input " . Input . "`r`n")
if (IsLabel(Input))
Gosub, %Input%
}
}
return
}
return
Exit
GuiClose:
ExitApp
; 1 = UP, 2 = DOWN
ScrollDir := 0
DoScroll:
ControlGetFocus, fcontrol, A
; WM_VSCROLL = 0x115, SB_LINEUP = 0, SB_LINEDOWN = 1
WinGetClass, ClassName, A
;Debug("Class " . ClassName)
if (InStr(ClassName, "Mozilla"))
{
IsMozilla := 1
}
else
{
IsMozilla := 0
}
Loop 3 {
if (ScrollDir = 1)
{
if (IsMozilla)
SendInput, {Up}
else
SendMessage, 0x115, 0, 0, %fcontrol%, A
}
else
{
if (IsMozilla)
SendInput, {Down}
else
SendMessage, 0x115, 1, 0, %fcontrol%, A
}
}
;Debug("Done")
Return
; Zoom down
012E020000010000:
ScrollDir := 2
GoSub, DoScroll
SetTimer, DoScroll, 80
return
; Zoom up
012D020000010000:
ScrollDir := 1
GoSub, DoScroll
SetTimer, DoScroll, 80
return
; All up
0100000000010000:
ScrollDir := 0
SetTimer, DoScroll, Off
return
Debug(msg)
{
OutputDebug, %msg%
return
}
Mem2Hex( pointer, len )
{
A_FI := A_FormatInteger
SetFormat, Integer, Hex
Loop, %len% {
Hex := *Pointer+0
StringReplace, Hex, Hex, 0x, 0x0
StringRight Hex, Hex, 2
hexDump := hexDump . hex
Pointer ++
}
SetFormat, Integer, %A_FI%
StringUpper, hexDump, hexDump
Return hexDump
}
Cheers,
Shaun