views:

712

answers:

0

My code uses a WH_MOUSE_LL hook to initially suppress all mouse input, unless the dwExtraInfo property is set to a certain value. The program is also registered for raw input for mouse devices, so that I can identify which device is responsible for the input. When I get a WM_INPUT message and determine the source, depending on the device I might just want to allow the event to take effect, in which case I recreate it with SendInput (tried mouse_event, which has been superseded, as well), providing data in the dwExtraInfo property. The idea is that the hook should then see this new injected event, see the extra info and not suppress it. Unfortunately, the injected event is never seen by the hook. The corresponding WM_INPUT message is seen by the window procedure though and SendInput returns 1, so it seems that the event is being generated. I have read that the context switches to the thread that installed the hook and that it must have a message loop. I believe my code meets that qualification (Since Application.Run() starts a message loop on the thread - I've also tried a handwritten Win32-style message loop in its place). Anyone have any ideas why this is happening and/or how to fix this?

The following code exhibits the problem. It should run with references to System and System.Windows.Forms.

Main class:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Win32;

public class LLMH : Form
{
    private static LowLevelMouseProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;
    private InputProcessor inputProcessor;
    private static IntPtr extraInfoPointer;
    private const int EXTRA_INFO = 1000;

    public static void Main()
    {
        _hookID = SetHook(_proc);
        LLMH app = new LLMH();
        app.inputProcessor = new InputProcessor();
        app.RegisterDevice(0x01, 0x02, (int)RawInputDeviceFlag.RIDEV_INPUTSINK);
        int extraInfo = EXTRA_INFO;
        LLMH.extraInfoPointer = new IntPtr(extraInfo);
        Application.Run();
        UnhookWindowsHookEx(_hookID);
    }

    public void RegisterDevice(ushort usagePage, ushort usage, int flags)
    {
        inputProcessor.RegisterDevice(usagePage, usage, flags, this.Handle);
    }

    protected override void WndProc(ref Message message)
    {
        switch (message.Msg)
        {
            case (int)WindowsMessage.WM_INPUT:
                Console.WriteLine("Received WM_INPUT.");

                RawInput rawInput = inputProcessor.GetRawInput(ref message);
                String name = inputProcessor.GetRawInputDeviceName(rawInput);

                Console.WriteLine("rawInput.extraInfo: {0}", rawInput.mouse.extraInformation);
                if (rawInput.mouse.extraInformation != EXTRA_INFO)
                {
                    Console.WriteLine("Creating local mouse event.");

                    //mouse_event(
                    //    (int)rawInput.mouse.flags,
                    //    rawInput.mouse.lastX,
                    //    rawInput.mouse.lastY,
                    //    rawInput.mouse.buttonData,
                    //    extraInfoPointer);

                    INPUT input = new INPUT();
                    input.type = (int)RawInputType.MOUSE;
                    input.mkhi.mi.dwFlags = (uint)rawInput.mouse.flags;
                    input.mkhi.mi.dx = rawInput.mouse.lastX;
                    input.mkhi.mi.dy = rawInput.mouse.lastY;
                    input.mkhi.mi.mouseData = rawInput.mouse.buttonData;
                    input.mkhi.mi.dwExtraInfo = extraInfoPointer;

                    Console.WriteLine("Return value of SendInput: {0}", SendInput(1, ref input, Marshal.SizeOf(input)));
                }

                base.WndProc(ref message);
                break;

            default:
                base.WndProc(ref message);
                break;
        }
    }

    private static IntPtr SetHook(LowLevelMouseProc proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            return SetWindowsHookEx((int)WindowsHook.WH_MOUSE_LL, proc,
                GetModuleHandle(curModule.ModuleName), 0);
        }
    }

    private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);

    private static IntPtr HookCallback(
        int nCode, IntPtr wParam, IntPtr lParam)
    {
        Console.WriteLine("\nIn hook.");
        if (nCode >= 0)
        {
            MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));

            if (hookStruct.dwExtraInfo == LLMH.extraInfoPointer)
            {
                Console.WriteLine("Extra info matches.  hookStruct.dwExtraInfo: {0}", hookStruct.dwExtraInfo);
            }
            else
            {
                Console.WriteLine("Extra info doesn't match.  hookStruct.dwExtraInfo: {0}", hookStruct.dwExtraInfo);
                return new IntPtr(-1);
            }
        }

        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook,
        LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
        IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize);

    [DllImport("user32.dll")]
    public static extern void mouse_event(Int32 dwFlags, Int32 dx, Int32 dy, Int32 dwData, IntPtr dwExtraInfo);
}

InputProcessor class:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Win32;

public class InputProcessor
{
    public RawInput GetRawInput(ref Message message)
    {
        uint size = 0;

        // Call with null pointer to get amount of memory required for buffer.
        GetRawInputData(
            message.LParam,
            (uint)RawInputDeviceCommand.RID_INPUT,
            IntPtr.Zero,
            ref size,
            (uint)Marshal.SizeOf(typeof(RawInputHeader)));

        IntPtr buffer = Marshal.AllocHGlobal((int)size);

        if (GetRawInputData(
            message.LParam,
            (uint)RawInputDeviceCommand.RID_INPUT,
            buffer,
            ref size,
            (uint)Marshal.SizeOf(typeof(RawInputHeader))) == size)
        {
            RawInput input = (RawInput)Marshal.PtrToStructure(buffer, typeof(RawInput));
            Marshal.FreeHGlobal(buffer);
            return input;
        }
        else
        {
            throw new ApplicationException("Failed to return Raw Input");
        }
    }

    public String GetRawInputDeviceName(RawInput rawInput)
    {
        uint length = 0;

        // Determine amount of memory to allocate for device name.
        GetRawInputDeviceInfo(
            rawInput.header.device,
            (uint)RawInputDeviceCommand.RIDI_DEVICENAME,
            IntPtr.Zero,
            ref length);

        String deviceName = String.Empty;

        if (length > 0)
        {
            IntPtr data = Marshal.AllocHGlobal((int)length);
            GetRawInputDeviceInfo(rawInput.header.device, (uint)RawInputDeviceCommand.RIDI_DEVICENAME, data, ref length);
            deviceName = (String)Marshal.PtrToStringAnsi(data);
            Marshal.FreeHGlobal(data);
        }

        return deviceName;
    }

    public void RegisterDevice(ushort usagePage, ushort usage, int flags, IntPtr windowHandle)
    {
        RawInputDevice[] inputDevices = new RawInputDevice[1];
        inputDevices[0].usagePage = usagePage;
        inputDevices[0].usage = usage;
        inputDevices[0].flags = flags;
        inputDevices[0].windowHandle = windowHandle;

        if (!RegisterRawInputDevices(inputDevices, (uint)inputDevices.Length, (uint)Marshal.SizeOf(inputDevices[0])))
        {
            throw new ApplicationException("Failed to register raw input devices.");
        }
    }

    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetRawInputDeviceInfo(
        IntPtr deviceHandle,
        uint command,
        IntPtr data,
        ref uint size);

    [DllImport("User32.dll")]
    public static extern bool RegisterRawInputDevices(
        RawInputDevice[] rawInputDevice,
        uint numDevices,
        uint size);

    [DllImport("User32.dll")]
    public static extern int GetRawInputData(
        IntPtr rawInput,
        uint command,
        IntPtr data,
        ref uint size,
        uint headerSize);


    [DllImport("user32.dll")]
    public static extern uint GetRawInputDeviceList(
        IntPtr rawInputDeviceList,
        ref uint numDevices,
        uint size);
}

Additional P/Invoke structs, enums, etc.

using System;
using System.Runtime.InteropServices;

namespace Win32
{
    public enum WindowsMessage
    {
        WM_NULL = 0x0000,
        WM_CREATE = 0x0001,
        WM_DESTROY = 0x0002,
        WM_MOVE = 0x0003,
        WM_SIZE = 0x0005,
        WM_ACTIVATE = 0x0006,
        WM_SETFOCUS = 0x0007,
        WM_KILLFOCUS = 0x0008,
        WM_QUIT = 0x0012,
        WM_INPUT = 0x00FF,
        WM_LBUTTONDOWN = 0x0201,
        WM_LBUTTONUP = 0x0202,
        WM_MOUSEMOVE = 0x0200,
        WM_MOUSEWHEEL = 0x020A,
        WM_RBUTTONDOWN = 0x0204,
        WM_RBUTTONUP = 0x0205
    }

    public enum WindowsHook
    {
        WH_MOUSE_LL = 14
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MSLLHOOKSTRUCT
    {
        public POINT pt;
        public uint mouseData;
        public uint flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    // Raw Input section.
    [StructLayout(LayoutKind.Sequential)]
    public struct RawInput
    {
        public RawInputHeader header;
        public RawInputMouse mouse;
        public RawInputKeyboard keyboard;
        public RawInputHID hid;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RawInputHeader
    {
        public RawInputType type;
        public int size;
        public IntPtr device;
        public IntPtr wParam;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RawInputMouse
    {
        public RawMouseFlags flags;
        public ushort buttonData;
        public RawMouseButtons buttonflags;
        public uint rawButtons;
        public int lastX;
        public int lastY;
        public uint extraInformation;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RawInputKeyboard
    {
        public ushort makeCode;
        public ushort flags;
        public ushort reserved;
        public ushort virtualKeyCode;
        public uint message;
        public ulong extraInformation;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RawInputHID
    {
        public int size;
        public int count;
        public IntPtr data;
    }

    public enum RawInputType
    {
        MOUSE = 0,
        KEYBOARD = 1,
        HID = 2
    }

    [Flags()]
    public enum RawMouseFlags : ushort
    {
        MOVE_RELATIVE = 0,
        MOVE_ABSOLUTE = 1,
        VIRTUAL_DESKTOP = 2,
        ATTRIBUTES_CHANGED = 4
    }

    [Flags()]
    public enum RawMouseButtons : ushort
    {
        None = 0,
        LeftDown = 0x0001,
        LeftUp = 0x0002,
        RightDown = 0x0004,
        RightUp = 0x0008,
        MiddleDown = 0x0010,
        MiddleUp = 0x0020,
        Button4Down = 0x0040,
        Button4Up = 0x0080,
        Button5Down = 0x0100,
        Button5Up = 0x0200,
        MouseWheel = 0x0400
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RawInputDeviceList
    {
        public IntPtr device;
        public uint type;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RawInputDevice
    {
        [MarshalAs(UnmanagedType.U2)]
        public ushort usagePage;
        [MarshalAs(UnmanagedType.U2)]
        public ushort usage;
        [MarshalAs(UnmanagedType.U4)]
        public int flags;
        public IntPtr windowHandle;
    }

    public enum RawInputDeviceFlag : int
    {
        RIDEV_REMOVE = 0x00000001,
        RIDEV_EXCLUDE = 0x00000010,
        RIDEV_PAGEONLY = 0x00000020,
        RIDEV_NOLEGACY = 0x00000030,
        RIDEV_INPUTSINK = 0x00000100,
        RIDEV_CAPTUREMOUSE = 0x00000200,
        RIDEV_NOHOTKEYS = 0x00000200,
        RIDEV_APPKEYS = 0x00000400,
        RIDEV_EXMODEMASK = 0x00000F0
    }

    public enum RawInputDeviceCommand : int
    {
        RID_INPUT = 0x10000003,
        RIDI_DEVICENAME = 0x20000007,
        RIDI_DEVICEINFO = 0x2000000b
    }

    public enum HIDUsage : ushort
    {
        Pointer = 0x01,
        Mouse = 0x02,
        Joystick = 0x04,
        Gamepad = 0x05,
        Keyboard = 0x06,
        Keypad = 0x07,
        SystemControl = 0x80,
        X = 0x30,
        Y = 0x31,
        Z = 0x32,
        RelativeX = 0x33,
        RelativeY = 0x34,
        RelativeZ = 0x35,
        Slider = 0x36,
        Dial = 0x37,
        Wheel = 0x38,
        HatSwitch = 0x39,
        CountedBuffer = 0x3A,
        ByteCount = 0x3B,
        MotionWakeup = 0x3C,
        VX = 0x40,
        VY = 0x41,
        VZ = 0x42,
        VBRX = 0x43,
        VBRY = 0x44,
        VBRZ = 0x45,
        VNO = 0x46,
        SystemControlPower = 0x81,
        SystemControlSleep = 0x82,
        SystemControlWake = 0x83,
        SystemControlContextMenu = 0x84,
        SystemControlMainMenu = 0x85,
        SystemControlApplicationMenu = 0x86,
        SystemControlHelpMenu = 0x87,
        SystemControlMenuExit = 0x88,
        SystemControlMenuSelect = 0x89,
        SystemControlMenuRight = 0x8A,
        SystemControlMenuLeft = 0x8B,
        SystemControlMenuUp = 0x8C,
        SystemControlMenuDown = 0x8D,
        KeyboardNoEvent = 0x00,
        KeyboardRollover = 0x01,
        KeyboardPostFail = 0x02,
        KeyboardUndefined = 0x03,
        KeyboardaA = 0x04,
        KeyboardzZ = 0x1D,
        Keyboard1 = 0x1E,
        Keyboard0 = 0x27,
        KeyboardLeftControl = 0xE0,
        KeyboardLeftShift = 0xE1,
        KeyboardLeftALT = 0xE2,
        KeyboardLeftGUI = 0xE3,
        KeyboardRightControl = 0xE4,
        KeyboardRightShift = 0xE5,
        KeyboardRightALT = 0xE6,
        KeyboardRightGUI = 0xE7,
        KeyboardScrollLock = 0x47,
        KeyboardNumLock = 0x53,
        KeyboardCapsLock = 0x39,
        KeyboardF1 = 0x3A,
        KeyboardF12 = 0x45,
        KeyboardReturn = 0x28,
        KeyboardEscape = 0x29,
        KeyboardDelete = 0x2A,
        KeyboardPrintScreen = 0x46,
        LEDNumLock = 0x01,
        LEDCapsLock = 0x02,
        LEDScrollLock = 0x03,
        LEDCompose = 0x04,
        LEDKana = 0x05,
        LEDPower = 0x06,
        LEDShift = 0x07,
        LEDDoNotDisturb = 0x08,
        LEDMute = 0x09,
        LEDToneEnable = 0x0A,
        LEDHighCutFilter = 0x0B,
        LEDLowCutFilter = 0x0C,
        LEDEqualizerEnable = 0x0D,
        LEDSoundFieldOn = 0x0E,
        LEDSurroundFieldOn = 0x0F,
        LEDRepeat = 0x10,
        LEDStereo = 0x11,
        LEDSamplingRateDirect = 0x12,
        LEDSpinning = 0x13,
        LEDCAV = 0x14,
        LEDCLV = 0x15,
        LEDRecordingFormatDet = 0x16,
        LEDOffHook = 0x17,
        LEDRing = 0x18,
        LEDMessageWaiting = 0x19,
        LEDDataMode = 0x1A,
        LEDBatteryOperation = 0x1B,
        LEDBatteryOK = 0x1C,
        LEDBatteryLow = 0x1D,
        LEDSpeaker = 0x1E,
        LEDHeadset = 0x1F,
        LEDHold = 0x20,
        LEDMicrophone = 0x21,
        LEDCoverage = 0x22,
        LEDNightMode = 0x23,
        LEDSendCalls = 0x24,
        LEDCallPickup = 0x25,
        LEDConference = 0x26,
        LEDStandBy = 0x27,
        LEDCameraOn = 0x28,
        LEDCameraOff = 0x29,
        LEDOnLine = 0x2A,
        LEDOffLine = 0x2B,
        LEDBusy = 0x2C,
        LEDReady = 0x2D,
        LEDPaperOut = 0x2E,
        LEDPaperJam = 0x2F,
        LEDRemote = 0x30,
        LEDForward = 0x31,
        LEDReverse = 0x32,
        LEDStop = 0x33,
        LEDRewind = 0x34,
        LEDFastForward = 0x35,
        LEDPlay = 0x36,
        LEDPause = 0x37,
        LEDRecord = 0x38,
        LEDError = 0x39,
        LEDSelectedIndicator = 0x3A,
        LEDInUseIndicator = 0x3B,
        LEDMultiModeIndicator = 0x3C,
        LEDIndicatorOn = 0x3D,
        LEDIndicatorFlash = 0x3E,
        LEDIndicatorSlowBlink = 0x3F,
        LEDIndicatorFastBlink = 0x40,
        LEDIndicatorOff = 0x41,
        LEDFlashOnTime = 0x42,
        LEDSlowBlinkOnTime = 0x43,
        LEDSlowBlinkOffTime = 0x44,
        LEDFastBlinkOnTime = 0x45,
        LEDFastBlinkOffTime = 0x46,
        LEDIndicatorColor = 0x47,
        LEDRed = 0x48,
        LEDGreen = 0x49,
        LEDAmber = 0x4A,
        LEDGenericIndicator = 0x3B,
        TelephonyPhone = 0x01,
        TelephonyAnsweringMachine = 0x02,
        TelephonyMessageControls = 0x03,
        TelephonyHandset = 0x04,
        TelephonyHeadset = 0x05,
        TelephonyKeypad = 0x06,
        TelephonyProgrammableButton = 0x07,
        SimulationRudder = 0xBA,
        SimulationThrottle = 0xBB
    }

    public enum HIDUsagePage : ushort
    {
        Undefined = 0x00,
        Generic = 0x01,
        Simulation = 0x02,
        VR = 0x03,
        Sport = 0x04,
        Game = 0x05,
        Keyboard = 0x07,
        LED = 0x08,
        Button = 0x09,
        Ordinal = 0x0A,
        Telephony = 0x0B,
        Consumer = 0x0C,
        Digitizer = 0x0D,
        PID = 0x0F,
        Unicode = 0x10,
        AlphaNumeric = 0x14,
        Medical = 0x40,
        MonitorPage0 = 0x80,
        MonitorPage1 = 0x81,
        MonitorPage2 = 0x82,
        MonitorPage3 = 0x83,
        PowerPage0 = 0x84,
        PowerPage1 = 0x85,
        PowerPage2 = 0x86,
        PowerPage3 = 0x87,
        BarCode = 0x8C,
        Scale = 0x8D,
        MSR = 0x8E
    }

    // SendInput section.
    [StructLayout(LayoutKind.Sequential)]
    public struct MOUSEINPUT
    {
        public int dx;
        public int dy;
        public uint mouseData;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBDINPUT
    {
        public ushort wVk;
        public ushort wScan;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct HARDWAREINPUT
    {
        public int uMsg;
        public short wParamL;
        public short wParamH;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct MOUSEKEYBDHARDWAREINPUT
    {
        [FieldOffset(0)]
        public MOUSEINPUT mi;

        [FieldOffset(0)]
        public KEYBDINPUT ki;

        [FieldOffset(0)]
        public HARDWAREINPUT hi;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct INPUT
    {
        public int type;
        public MOUSEKEYBDHARDWAREINPUT mkhi;
    }

}

Program output after one WM_LBUTTONDOWN event:

In hook.
Extra info doesn't match.  hookStruct.dwExtraInfo: 0
Received WM_INPUT.
rawInput.extraInfo: 0
Creating local mouse event.
Return value of SendInput: 1
Received WM_INPUT.
rawInput.extraInfo: 1000