winapikeyboardpyqtbarcode-scannerraw-input

How to emulate WM_KEYDOWN, WM_KEY* from a WM_INPUT handler registered with RIDEV_NOLEGACY?


I have a system with two HID keyboards (actually, one's a barcode scanner.)

I registered for raw input with RIDEV_NOLEGACY to block the system from creating WM_KEY* messages for the barcode scanner, which tediously also blocks the messages from the other keyboard.

My goal is to keep the WM_* messages for any keyboard device that isn't the barcode scanner.

Basically, I need to either (1) create the WM_* messages myself, and post them to my HWND from the WndProc that received the WM_INPUT, or (2) predict the WM_* messages the system will generate, and ignore them if they came from the barcode scanner.

I created a working implementation of (2), which works great on XP but fails to block anything on Windows 7. In fact, on Windows 7 it seems like I'm only receiving WM_INPUTs even without the RIDEV_NOLEGACY flag.

I'm now trying method (1), which is arguably "more correct", but I can't seem to find a way to do this completely correctly.

My environment is Python 2.6 using PyQt. I'm sending messages directly to a window created by PyQt, and I've hooked into it's WndProc with a Win32 event filter.

class wm_lparam(Structure):
    _fields_ = [("repeat_count", c_short),
                ("scancode", c_byte),
                ("extended_key", c_int, 1),
                ("reserved", c_int, 4),
                ("context_code", c_int, 1),
                ("prev_state", c_int, 1),
                ("transition_state", c_int, 1),
               ]

assert sizeof(wm_lparam) == 8, sizeof(wm_lparam)

WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101

WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105

ALL_WM_KEYDOWN = (WM_KEYDOWN, WM_SYSKEYDOWN)
ALL_WM_KEYUP = (WM_KEYUP, WM_SYSKEYUP)
VK_SHIFT = 0x10
VK_LSHIFT = 0xA0
VK_RSHIFT = 0xA1

# These values are filled in by my WM_INPUT handler and the RAWINPUT struct
@staticmethod
def _synthesize_wm_legacy(hwnd, wm, vk, scancode, modifider_keys=None):
    kbState_old = (c_byte*255)()
    kbState = (c_byte*255)()

    def keydown(vk):
        return bool(user32.GetAsyncKeyState(vk) & 0x8000)

    kbState[VK_SHIFT] = 0x80 if keydown(VK_SHIFT) else 0
    kbState[VK_LSHIFT] = 0x80 if keydown(VK_SHIFT) else 0

    user32.GetKeyboardState(kbState_old)
    user32.SetKeyboardState(kbState)
    lParam = wm_lparam()
    lp = c_uint.from_address(ctypes.addressof(lParam))
    lParam.repeat_count = 0
    lParam.scancode = scancode
    lParam.extended_key = 0

    if wm in ALL_WM_KEYDOWN:
        lParam.context_code = 0
        lParam.prev_state = 0
        lParam.transition_state = 0

    if wm in ALL_WM_KEYUP:
        lParam.repeat_count = 0
        lParam.context_code = 0
        lParam.prev_state = 1
        lParam.transition_state = 1

    lp = lp.value
    if wm in ALL_WM_KEYUP:  # Seems ctypes doesn't like my struct definition.
        lp |= 1 << 30
        lp |= 1 << 31

    log.debug("Posting %s %s %s %08x\n%s"%(hwnd, wm_str(wm), vk, lp, lParam.dump_s()))
    user32.SendMessageA(hwnd, wm, vk, lp)
    user32.SetKeyboardState(kbState_old)

This code works, but certain things (like holding the Shift key, etc) fail. Also very strange is that when using SendMessage, the letters I type are in uppercase, but switching to PostMessage makes them lowercase. I can probably solve this via GetKeyState/SetKeyState, but I was hoping somebody could give me some answers.

In addition, I'm posting these messages back onto PyQt's queue, but the application fails to process them until a real event is system-generated. That is, If I type a sentence into a text box, nothing shows up until I then move my mouse over the window. The messages seem queued until a real event happens. Any suggestions?


Clarification:

This is a window in my own process, created by PyQt. I have gotten it's HWND, and hooked the raw input notification up to it. In the window procedure for WM_INPUT on this HWND, I want to send message to my own HWND to duplicate the "legacy" WM_KEY* messages that I previously disabled to filter them. Again, this all happens in my own process, in my own thread.


Update:

Shift state detection simply doesn't work. No matter what, I am getting all capital keys. Any advice?


I was unable to solve this in pure Win32, and I've gotten only a half solution since I'm using PyQt. In case anyone is interested though, here's the code I'm using for that portion:

class BarcodeQtEventFilter(QtCore.QObject):
    def __init__(self, parent, *args, **kwargs):
        self.log = logging.getLogger(__name__ + '.keyevent')
        self.app = parent
        self.input_to_suppress = list()
        super().__init__(parent, *args, **kwargs)
    
    def ignoreKey(self, which):
        """On WM_INPUT from the device, call this with the reported VKey"""
        self.input_to_suppress.append(which)

    def eventFilter(self, object, event):
        if event.type() == QtCore.QEvent.KeyPress:
            if self.input_to_suppress:
                if event.nativeVirtualKey() in self.input_to_suppress:
                    z = None

                    # This loop eats the suppression buffer until the VK pressed is found.
                    # Fixes dupes for WM key up/down, etc.
                    while z != event.nativeVirtualKey():
                        z = self.input_to_suppress.pop(0)

                    self.log.debug("Ate key press %s (%s)", event.key(), event.text())
                    return True

                else:
                    self.log.debug("Future suppressed input: %s", self.input_to_suppress)

            self.log.debug("Allowing key press %s (%s)", event.key(), event.text())

        return False

Solution

  • This is not fixable as-is, you cannot control the keyboard state. The receiving app will use GetKeyState() to check if the Shift, Ctrl or Alt key is down. SetKeyState() doesn't work, it only changes the keyboard state of your process, not the process that gets the messages.

    Use SendInput() instead. A window in the target process must have the focus.