Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

Native HID support


  • Please log in to reply
25 replies to this topic
MrB
  • Guests
  • Last active:
  • Joined: --
More and more people are using USB devices now. Mainly all new keyboards have special keys that cannot be recognized with the standard keyboard drivers. Ie. logitech and MS models with F-Lock keys.

Either you have to have the vendors software running wasting your system resources if you want to make use of the additional keys. Or you can use Michas dll which is quite dificult to set up and also requires additional resources.

That's why I propose a native HID support in autohotkey. :D Allthough I do not know ho difficult that is... :O :O

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I recently discovered that Micha's dll is not actually required. RegisterRawInputDevices() registers the window to receive WM_INPUT messages, then the WM_INPUT handler uses GetRawInputData() and NumGet() to get the data of the message.

A more friendly script wrapper could be written. What kind of features/interface/usage would you expect? You still need to know the Usage and UsagePage of the device, which is where Micha's script comes in handy. (That could also be rewritten, but there's not much sense reinventing the wheel.)

From what I've seen, the only devices with data in a standard format are keyboards and mice, which are already supported via the keyboard/mouse hooks. Raw HID data has to be interpreted differently depending on the device.

For instance, my Logitech MX610 has one mouse device and one HID. All functions reported by the mouse device are supported by the mouse hook, except for the wheel tilt (horizontal scroll). Bytes 2 and 3 of the HID data seem to correspond to the e-mail (8A 01) and chat (BC 01) buttons, and byte 4 corresponds to volume up (E9), volume down (EA), and mute (E2). Button press and release can both be detected, as long as you only press one button from each group at a time (i.e. chat and volume work together, but chat and e-mail conflict.)

How should HID support should work for such devices? What would be easier if it were built into AutoHotkey?

Superfraggle
  • Members
  • 1019 posts
  • Last active: Sep 25 2011 01:06 AM
  • Joined: 02 Nov 2004
Im interested in detecting multiple separate hid devices, so my usb keyboard can be used for controlling apps on my second monitor only.

Could this be done with HID support.
Steve F AKA Superfraggle

http://r.yuwie.com/superfraggle

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Not entirely. It isn't possible (via registered input/HID support) to prevent the keystrokes from a specific keyboard from being sent to the active window. It might be possible to block the keystrokes using a keyboard hook, but there's no guarantee you'd be able to match up the keyboard hook events to the HID events (and indirectly, which keyboard you are typing on.)

I suppose you could block all physical key events using a keyboard hook, and redirect input from a specific keyboard by using registered input...

MrB
  • Guests
  • Last active:
  • Joined: --

I recently discovered that Micha's dll is not actually required. RegisterRawInputDevices() registers the window to receive WM_INPUT messages, then the WM_INPUT handler uses GetRawInputData() and NumGet() to get the data of the message.

That is interesting news. I never worked with RegisterRawInputDevices or GetRawInputData commands. Could you post an example for me?

the only devices with data in a standard format are keyboards and mice, which are already supported via the keyboard/mouse hooks

Right, but only to some extend since many special keys cannot be detected by autohotkey. And is a standard really important? As long as you can see each HID button in the key history, you can assign actions to them.

How should HID support should work for such devices? What would be easier if it were built into AutoHotkey?

Simply put: So that there is as little difference to "normal" keys. Means: You have a command to enable HID support or even better, HID support is automatically enabled on all devices and you can manually disable it for some devices.
And of course: implement a key detection in the key history.
Do you know girder? There is a hid plugin. You turn it on, select the HID device to accept commands from and after these two steps, the user won't see a difference...

It isn't possible (via registered input/HID support) to prevent the keystrokes from a specific keyboard from being sent to the active window.

Then I do not understand why you always have to select the HID device to listen to...


Again, I am knowbody to judge how difficult implementation would be. The girder example shows that its possible...

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

I never worked with RegisterRawInputDevices or GetRawInputData commands. Could you post an example for me?

I hastily put this together when I was trying to detect my mouse's wheel tilt. It should show input from keyboards, mice or raw devices. You'll still need Micha's script to find the Usage and UsagePage (except for the keyboard and mouse, which I think are consistently 6,1 / 2,1.)
#NoEnv

; Disable this line if you're running from an editor that catches StdOut
; (like PSPad or SciTE.)
DllCall("AllocConsole")

DetectHiddenWindows, On
Process, Exist
hwnd := WinExist("ahk_class AutoHotkey ahk_pid " ErrorLevel)
DetectHiddenWindows, Off

OnMessage(0xFF, "WM_INPUT")

; 6, 1 = keyboard
; 2, 1 = mouse

Usage       = 6
UsagePage   = 1

VarSetCapacity(dev, 12, 0)
NumPut(UsagePage,   dev, 0, "UShort")
NumPut(Usage,       dev, 2, "UShort")
NumPut(0x100,       dev, 4) ; dwFlags = RIDEV_INPUTSINK (don't require foreground)
NumPut(hwnd,        dev, 8)


ret := DllCall("RegisterRawInputDevices"
    , "uint", &dev  ; pRawInputDevices (pointer to an array of RAWINPUTDEVICE)
    , "uint", 1     ; uiNumDevices
    , "uint", 12)   ; cbSize (size of a RAWINPUTDEVICE structure)
if (ErrorLevel or !ret) {
    MsgBox, RegisterRawInputDevices failed.
    ExitApp
    }

return


WM_INPUT(wParam, lParam)
{
    Critical
    
    ; foreground := ! (wParam & 0xFF)
    
    ; Get required buffer size.
    DllCall("GetRawInputData", "uint", lParam, "uint", 0x10000003
        , "uint", 0, "uint*", size, "uint", 16)
    
    VarSetCapacity(raw, size, 0)
    
    ; Get raw input data from handle (lParam)
    ret := DllCall("GetRawInputData", "uint", lParam, "uint", 0x10000003
        , "uint", &raw, "uint*", size, "uint", 16, "int")
    
    if (ErrorLevel or ret = -1) {
        StdOut("GetRawInputData -- Error " ErrorLevel " LA " A_LastError)
        return
        }

    type := NumGet(raw)
    ; #define RIM_TYPEMOUSE       0
    ; #define RIM_TYPEKEYBOARD    1
    ; #define RIM_TYPEHID         2
    
    hDevice := NumGet(raw, 8)
    
    if type = 1  ; RIM_TYPEKEYBOARD
    {
    SetFormat, INTEGER, H
        sc      := NumGet(raw, 16, "UShort")
        flags   := NumGet(raw, 18, "UShort")
        vk      := NumGet(raw, 22, "UShort")
        msg     := NumGet(raw, 24, "UInt")  ; WM_KEYDOWN, WM_SYSKEYDOWN, etc.
        
        vk_a := SubStr(vk, 3)
        sc_a := SubStr(sc, 3)
        StringUpper, vk_a, vk_a
        StringUpper, sc_a, sc_a
        
        if (StrLen(vk_a)<2)
            vk_a = 0%vk_a%
        sc_a := ((flags & 2) ? "1" : "0") . (StrLen(sc_a)<2 ? "0" : "") . sc_a
        
        act := (flags & 1) ? "up" : "dn"
        
        StdOut(vk_a " " sc_a " " act "  msg: " msg "  flags: " flags "  dev: " hDevice)
    }
    else if type = 2  ; RIM_TYPEHID
    {
    SetFormat, INTEGER, H
        count := NumGet(raw, 20) ; dwCount
        if count > 1
            StdOut("dev: " hDevice)
        n = 0
        Loop, %count%
        {
            size := NumGet(raw, 16) ; dwSizeHid
            VarSetCapacity(hex, size*3)
            Loop, %size%
            {
                char := SubStr(NumGet(raw, 24+n+A_Index-1, "UChar"), 3)
                StringUpper, char, char
                if (StrLen(char) < 2)
                    char = 0%char%
                hex = %hex% %char%
            }
            n += size
            if !size
                hex = "--"
            StdOut((count=1 ? "dev: " hDevice " -- " : "") hex)
        }
    }
    else if type = 0  ; RIM_TYPEMOUSE
    {
        lastX := NumGet(raw, 28, "int")
        lastY := NumGet(raw, 32, "int")
        delta := NumGet(raw, 22, "Short")   ; usButtonData
    SetFormat, INTEGER, H
        flags := NumGet(raw, 16, "UShort")  ; usFlags
        btnfl := NumGet(raw, 20, "UShort")  ; usButtonFlags
        
        StdOut("dev: " hDevice " flags: " flags "  x" lastX " y" lastY "  btnfl: " btnfl " delta: " delta)
    }
}

StdOut( text )
{
    static hStdOut=-1

    if (hStdOut = -1)
    {
        hStdOut := DllCall("GetStdHandle", "UInt", -11) ; -11=STD_OUTPUT_HANDLE

        if ErrorLevel
            return -1
    }

    text .= "`n"

    ret := DllCall("WriteFile"
        , "UInt", hStdOut       ; hFile
        , "UInt", &text         ; lpBuffer
        , "UInt", StrLen(text)  ; nNumberOfCharsToWrite
        , "UIntP", bytesWritten ; lpNumberOfCharsWritten
        , "UInt", 0)            ; lpOverlapped

    return bytesWritten
}
It turns out the mouse hook supports "WM_MOUSEHWHEEL" (Vista-only), so I didn't use WM_INPUT. Actually, WM_INPUT can't seem to pick up the wheel tilt on Windows XP (I'm on Vista.) It shows mouse messages, but all the "tilt" messages are empty...

Right, but only to some extend since many special keys cannot be detected by autohotkey. And is a standard really important?

Yes - raw HID data is sent as a blob of binary data. For instance, one of the devices associated with my mouse sends "03 00 00 E2 00" for mute, "03 00 00 E9 00" for volume up, but "03 BC 01 E9 00" if I was already holding the chat button. It's not a simple "pressed/released this button" message, so adding generic support for it to AutoHotkey would be difficult.

Can you give me an example of a "special key"? My keyboard has no keys that AutoHotkey doesn't pick up (except for a Function lock key which can't be picked up via WM_INPUT either.)

When a keyboard is registered for raw input, the WM_INPUT messages contain the scancode (aka MakeCode) and vk code of the key. This lead me to believe the keyboard hook should be able to pick up everything that WM_INPUT picks up for a keyboard device. With your "special keys", does WM_INPUT show a MakeCode/scancode? If so, are you sure the keyboard hook doesn't pick it up?

How should HID support should work for such devices?

Perhaps more importantly, how would you identify a button on the device, or the device itself?

It isn't possible (via registered input/HID support) to prevent the keystrokes from a specific keyboard from being sent to the active window.

Then I do not understand why you always have to select the HID device to listen to...

You've lost me... how is that related? Windows doesn't send the WM_INPUT messages by default, since applications generally don't need them. That is why you "have to select the HID device to listen to." WM_INPUT doesn't allow you to block the input messages from the system, but it does allow you to determine which device sent the message.

Micha
  • Members
  • 539 posts
  • Last active: Dec 31 2011 01:43 PM
  • Joined: 15 Nov 2005
Hi,
You have to register a device so windows sends you the desired data but I have not found a way to block input for other applications. If 10 applications have registered a device, each will receive the data. HID devices are just not keys

Of course my dll is not needed at all. You can achive the same results with lots of dllcalls and NumGet-calls. The dll is just calling the api. I've written the dll, because I can wrap a lot of stuff inside the dll and exposing only the needed functions. But it can be done without a dll, that's true.

Integrating HID-devices into ahk: You can't use it with Windows 98 / 2000.
Perhaps Chris has to compile and test 2 versions. One with HID support and one for older systems without HID-Support. (If I'm the devloper I wouldn't like to do it)
Perhaps more and more people are using HID-devices, but how many ahk-users will do it? The autohotkey.exe will definitly grow in size and will need more space at runtime.
The data received is not like a key. The data is binary and could be 4000500640 or even AA3356BC45FE657664A4E5B65D456F456E54AA5004000EE00412AA.
So at the moment, you can use a dll or include a script with the functions.

Ciao
Micha

MrB
  • Guests
  • Last active:
  • Joined: --
@lexikos
Thanks for the script. That is exactly why I started the discussion - as a lets say average user of autohotkey looking over your script I find it dificult to implement for myself. That's why I am calling for simple commands also the unexperienced user can use.


Can you give me an example of a "special key"? My keyboard has no keys that AutoHotkey doesn't pick up (except for a Function lock key which can't be picked up via WM_INPUT either.)

I am using am Logitech S 510 cordless keyboard. It has a F-Mode button that switches the layout of the F1-F12 keys from normal function to special ie programable funtions. Autohotkey does detect the normal F1-F12 but after the F Mode button is pressed (ie special functions activated) none of F1-F12 are recognized.
With Michas skript or girder HID plugin these keys are detected and give scancodes like 4854564851 or 4854555555.


You've lost me... how is that related? Windows doesn't send the WM_INPUT messages by default, since applications generally don't need them. That is why you "have to select the HID device to listen to." WM_INPUT doesn't allow you to block the input messages from the system, but it does allow you to determine which device sent the message.

Sorry... perhaps I was unprecise or I misunderstood the concept. With girders HID plugin you have to select the device you are listening to. Thats why I assumed you get only messages from the device selected and that allows you to use different layouts with different devices.

@Micha

Perhaps more and more people are using HID-devices, but how many ahk-users will do it?

Isn't it rather the question how many ahk-users are not using it because it is difficult to use. Sorry, I don't want to put your efforts down at all - but you have to recognize that for a normal user it is quite a big step 1) to understand that HID support is needed, 2) get information on a possible solution in the forum, 3) locate your dll solution or lexikos script, 4) understand it and 5) implement it in your own ahk startup script.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

looking over your script I find it dificult to implement for myself

That isn't surprising. I wrote that script solely to determine what was possible with raw input via the script. It was never intended for any real use.

I am using am Logitech S 510 cordless keyboard. It has a F-Mode button that switches the layout of the F1-F12 keys from normal function to special ie programable funtions. Autohotkey does detect the normal F1-F12 but after the F Mode button is pressed (ie special functions activated) none of F1-F12 are recognized.

Interesting. My Logitech EX110 (cheap cordless multimedia keyboard) has an F-lock key, but it can't be detected by registered input. When F-lock is off, my F1-F12 keys have the "extended flag" set. For instance, F1 is normally SC03B. With F-lock off, it's SC13B.

With Michas skript or girder HID plugin these keys are detected and give scancodes like 4854564851 or 4854555555.

4854564851 and 4854555555 are impossible scancodes, barring errors. MakeCode (aka scancode) is a USHORT - i.e. its entire range of values is 0 to 0xFFFF (65535).

Scancodes are normally between 0 and 0x7F (127). (AutoHotkey adds 0x100 if the extended key flag is set.) I suppose keys outside the normal range might be reported via WM_INPUT but not the keyboard hook.

Thats why I assumed you get only messages from the device selected and that allows you to use different layouts with different devices.

The only way to register a keyboard for raw input is by registering Usage=6, UsagePage=1. This registers all keyboards. It is possible to determine which device a WM_INPUT message came from, though.


I've updated the script in my previous post to also output the flags of the WM_INPUT message, since they may be important. If the script can detect your "special" keys, please copy the output and post it here. You can normally copy text from a console window by right-clicking, clicking Select All, then right-clicking again. Strangely, the context menu doesn't seem to work with that script (for me?), but you can still access it via the window's system menu, and "Edit". After you've posted the output of your keys, I might try to write a more user-friendly script...

The data received is not like a key. The data is binary and could be 4000500640 or even AA3356BC45FE657664A4E5B65D456F456E54AA5004000EE00412AA.

That's what I was thinking, but (raw) keyboard devices do report data in a consistent format. If raw input reports keys that the keyboard hook doesn't, it might be worth writing a more user-friendly script to catch them.


Of course my dll is not needed at all. You can achive the same results with lots of dllcalls and NumGet-calls.

Lots?

There is one function on my mouse that I couldn't program without raw input: the chat button. I'm currently using:[*:1jl4aaex]Four NumPut() calls to prepare the structure for RegisterRawInputDevices().
[*:1jl4aaex]DllCall("RegisterRawInputDevices"...) once.
[*:1jl4aaex]GetRawInputData() twice (once to get the required buffer size, once to get the data) per WM_INPUT message.
[*:1jl4aaex]Four calls to NumGet() per WM_INPUT message.If I assumed that no other devices would ever be registered by the script (a fair assumption), I could remove the first GetRawInputData() and three of the NumGet() calls, since my device always sends the same amount and format of data. That would leave one DllCall and one NumGet() per WM_INPUT message.

So no, that isn't lots. :lol:

I've written the dll, because I can wrap a lot of stuff inside the dll and exposing only the needed functions.

For me, your wrapper exposes more than the needed functions. :)

(Btw, I imagine writing the dll might've been easier, since you don't need to worry about offsets in structs, etc. as C++ has good struct support...)

Micha
  • Members
  • 539 posts
  • Last active: Dec 31 2011 01:43 PM
  • Joined: 15 Nov 2005
Hi,

4854564851 and 4854555555 are impossible scancodes, barring errors. MakeCode (aka scancode) is a USHORT - i.e. its entire range of values is 0 to 0xFFFF (65535).

I've changed the script-section when RAW-InputData is received. The old script had the following bug: The dllcall returned the raw data
001020 (hex)
When displaying the value, each number was interpreted wrong by the script. The number was converted automatically. Binary 1 was converted to the numer one and was displayed as 49. Each number was converted to the corresponding ascii value (0=48 1=49 1=50 ...)
So the value 001020 gets translated into 00 00 49 00 51 00 which is wrong. I've changed the script to display the correct value 00 10 20 but it's possible i've missed the mouse and keyboard section. So the correct value for the number mentioned above is 00 06 08 00 02
I'll have a look at the script.

Lots? (dll calls)

You're right, the dll-calls are almost equal to the windows api or my dll. If you know the device sending you the data (mouse, keyboard, raw) you do not have to call a lot of api-functions, but if you have registered all devices, you have to get infos about the type (1), the devicenumber (2).
It depends on the device type what struct you have to prepare and after another dll call you have to get the result out of that struct.

As I said, everything can be done within ahk (without a dll) but I (personally) found it easier to write a dll (which can be debugged) than using tons (ok, not sooo much) ExtractIntegers (NumGet wasn't part of ahk that time) and testing the script for hours :-)

Isn't it rather the question how many ahk-users are not using it because it is difficult to use.

I've written 3 or 4 dlls for autohotkey and I've missed to write functions to wrap the dllcall. I've heard it not the first time, that the /or the other dll is difficult to use.
I see.... I have to spent more time to present it the right way.
After trying something new every time with every new dll, I was glad that it was working and I've started to work on new things. In the future I'll spent more time on the decoration of the dll calls :-)

Ciao
Micha

MrB
  • Guests
  • Last active:
  • Joined: --

If the script can detect your "special" keys, please copy the output and post it here.

I would if I could. Running the script, I don't get output for the mentioned F Mode keys. (F1-F12 after pressing F Mode).

4854564851 and 4854555555 are impossible scancodes, barring errors. MakeCode (aka scancode) is a USHORT - i.e. its entire range of values is 0 to 0xFFFF (65535).

Scancodes are normally between 0 and 0x7F (127). (AutoHotkey adds 0x100 if the extended key flag is set.) I suppose keys outside the normal range might be reported via WM_INPUT but not the keyboard hook.

What do I make out of this? I used this script http://www.autohotke...l&highlight=hid and got these codes 4854564851 and 4854555555. Did you mind me using the word scancodes or is there more behind these lines I don't understand?

It is possible to determine which device a WM_INPUT message came from, though.

So it should be possible to have different devices with different key assignments - which is what Superfraggle was asking.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

I used this script http://www.autohotke...l&highlight=hid and got these codes 4854564851 and 4854555555.

Right, those aren't scancodes, since that script doesn't register a keyboard device. It registers Usage=1, UsagePage=12. On my system, the only device that uses those values is a device used by my mouse to report its extra buttons.

The script you linked to makes a couple mistakes, including starting at offset=1 instead of offset=0, and concatenating decimal values with inconsistent lengths (e.g. 101, 9 and 8 become "101908".) Actually, the data it seems to be reporting for my mouse is totally off... even after fixing those issues. :?

Since the device is not a standard keyboard device, it comes back to this:

The data received is not like a key. The data is binary and could be 4000500640 or even AA3356BC45FE657664A4E5B65D456F456E54AA5004000EE00412AA.

So it should be possible to have different devices with different key assignments - which is what Superfraggle was asking.

Yes, but the keys would still be seen by the system (like ~hotkeys.)

I've changed the script-section when RAW-InputData is received.

Which script is that? Your HID-device-support script? Actually, part of the reason I wrote my own script was that your script was reporting a different value for the last byte of data each time I started the script. The rest of the data was fine...

As I said, everything can be done within ahk (without a dll) but I (personally) found it easier to write a dll (which can be debugged) than using tons (ok, not sooo much) ExtractIntegers (NumGet wasn't part of ahk that time) and testing the script for hours

Yes, I can see why. A while ago I wrote a script to convert C struct definitions to NumGet() calls, so using NumGet and NumPut wasn't difficult. (I wrote the script after making numerous mistakes interpreting DEVMODE.)

I would if I could. Running the script, I don't get output for the mentioned F Mode keys.

Btw, you should get raw data output from your F keys if you change the Usage to 1 and UsagePage to 12 (in the script I posted), assuming you didn't change the Usage & UsagePage in that Keyboard Media Keys script.

MrB
  • Guests
  • Last active:
  • Joined: --

Btw, you should get raw data output from your F keys if you change the Usage to 1 and UsagePage to 12 (in the script I posted), assuming you didn't change the Usage & UsagePage in that Keyboard Media Keys script.

True. I get the follwing "raw data output" starting with F1 to F12
dev: 7996371 -- 03 41 10 00 00 ;F1
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 42 10 00 00;F2
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 43 10 00 00;F3
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 44 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 45 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 46 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 47 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 48 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 49 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 4A 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 4B 10 00 00
dev: 7996371 -- 03 00 00 00 00
dev: 7996371 -- 03 4C 10 00 00
dev: 7996371 -- 03 00 00 00 00

MrB
  • Guests
  • Last active:
  • Joined: --
Funny thing is: with Usage1, UsagePage 12 I only get FMode F1-F12. No other key. Listening to raw data I would have expected to geht also raw data on other keys.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Interesting. What you describe is very similar to my mouse. Not only does the 1,12 device report only the extra buttons, but the data is very similar:
dev: 131177 -- 03 00 00 E9 00  - vol up

dev: 131177 -- 03 00 00 EA 00  - vol down

dev: 131177 -- 03 00 00 E2 00  - mute

dev: 131177 -- 03 8A 01 00 00  - email

dev: 131177 -- 03 BC 01 00 00  - chat

dev: 131177 -- 03 00 00 00 00  - none (all released)
Perhaps it's a Logitech design? I guess they add that pseudo-device to report hardware events that aren't supported by the standard devices.