views:

27

answers:

1

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 keybaord 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

  1. Predict the WM_* messgaes 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 win7 it seems like i'm only reciving WM_INPUTs even without the RIDEV_NOLEGACY flag)

I'm now trying method 1, which is arguably 'more correct', but I can't seems 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 switchign to PostMessage makes them lowercase. I can probably solve this via Get/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 sytem 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 sendmessage 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 BarcodeQtEventFiler(QtCore.QObject):
    def __init__(self, parent, *args):
        self.log = logging.getLogger(__name__ + '.keyevent')
        self.app = parent
        self.input_to_surpress = list()
        super(BarcodeQtEventFiler, self).__init__(parent, *args)

    def ignoreKey(self, which):
        """On WM_INPUT from the device, call this with the reported VKey"""
        self.input_to_surpress.append(which)

    def eventFilter(self, object, event):
        if event.type() == QtCore.QEvent.KeyPress:
            if self.input_to_surpress:
                if event.nativeVirtualKey() in self.input_to_surpress:
                    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_surpress.pop(0)
                    self.log.debug("Ate key press %s (%s)", event.key(), event.text())
                    return True
                else:
                    self.log.debug("Future surpressed input: %s", self.input_to_surpress)
            self.log.debug("Allowing key press %s (%s)", event.key(), event.text())
        return False
+2  A: 

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.

Hans Passant
Ah, but I am the receiving app. I'll update the summary to be more clear.
CB
Hmm, didn't see that coming. Why don't you just handle the message directly? SetKeyState() to control the modifier keys should definitely work.
Hans Passant
I'm using a UI framework (specifically PyQt). I'd want to make Win32 do it, so that PyQt does it, mostly so that I don't have to rely on getting Qt's specifics right, and partly so this portion of the code (the barcode rawinput reader) can remain a plugin, and not have intimate knowledge of the ui. Basically, the only requirements I want the barcode portion to have is 'call me in your wndproc, and i'll handle the rest, and call you if they scan something'. Thats the part i'm writing now.
CB
Additional Question: If I /do/ use SendInput, will that cause my WM_INPUT handler to be called again? If not, I might just end up going that route.
CB
No, you're sending keystrokes, INPUT.type = INPUT_KEYBOARD
Hans Passant
Unfortunately I ran out of time to implement this in native Win32, so I have a hackish solution that lets the Qt portion know that a QKeyPress is coming in, writing a Qt filter, and ignoring it in that case. Not pretty, but it works. If I get to play around with it some other time, I'll try out SendInput. Thanks for your help.
CB