5

I am attempting to control two "mice". The primary mouse (mice) are used for controlling the cursor for normal UI control (which I don't want to intercept) and a secondary "mouse" which should only be used as input into the application I am writing and ignored otherwise.

I am successfully capturing the mouse messages and filtering as desired. I can also successfully capture the mouse inputs,even when my app is not in focus (as desired).

The only remaining issue is that I can't seem to keep the mouse from interacting with other applications. So I can essentially spy on the mouse, but not fully consume the message.

I "believe" I am preventing the "base.WndProc(ref message)" from getting called when I am processing the filtered mouse messages, but the system still seems to be getting the mouse clicks.

Is C# (Visual Studio Express 2010) able to do this?

Thank you for any assistance you might be able to give.

ChronoFish
  • 3,589
  • 5
  • 27
  • 38
  • 1
    heq's answer seems the most correct. Overriding base.WndProc only controls the Windows messages *your* application receives, but it has nothing to do with controlling the Windows messages of other applications. A global mouse hook is the way to go. It's actually really popular. – Jason Dec 08 '12 at 08:57
  • A mouse hook is only half the battle. I actually need both. The hook is used to prevent the click form going to other apps, but I need raw mouse so I which actual device did the clicking. The real answer is that C# (.net) is not the right tool - sadly. – ChronoFish Dec 13 '12 at 03:42
  • What do you mean by you need raw mouse so you [know] which actual device did the clicking? On hooking a mouse click, you can determine the active window (GetActiveWindow()) and the location of the click, and then you can determine whether that click occurred on your form or not. – Jason Dec 13 '12 at 03:46
  • I'm using two mice. One for standard mouse navigation one for an alternate input device. Rawmouse can tell the difference between devices, mousehook can not. I want the primary "mouse" to act like a mouse, but I want secondary mouse to only send input to my app (otherwise it will send "mouse" clicks inadvertently to other applications) – ChronoFish Dec 13 '12 at 03:55
  • I finally found someone looking to do what I'm doing - and his thread has a link to a library that appears to solve the problem: http://stackoverflow.com/questions/11370911/wndproc-in-my-wpf-app-not-handling-wm-input-event – ChronoFish Dec 28 '12 at 02:24

4 Answers4

3

Raw input might be what you're looking for. Here is an MSDN discussion with a similar question which has link to a codeproject article on handling mutliple keyboards in C#.

Bikonja
  • 909
  • 5
  • 15
  • This is the primary reference that got me as far as I did. It "works" except for limiting input in other applications. Thanks though. – ChronoFish Dec 03 '12 at 18:39
  • I don't think it's possible to filter input globally, judging from [this](http://msdn.microsoft.com/en-us/library/windows/desktop/ms645543(v=vs.85).aspx) article. The sentence that makes me think so is: "Note that the mouse and the keyboard are also HIDs, so data from them can come through both the HID message WM_INPUT and from traditional messages. An application can select either method by the proper selection of flags in RAWINPUTDEVICE." – Bikonja Dec 03 '12 at 19:48
  • I'll award you with the answer because it's "not possible" in C# or .net. Though it is possible - but you have to go with a non .net DLL which basically means programming in C/C++ – ChronoFish Dec 13 '12 at 03:58
1

You can use global mouse hook. I don't remember where I've found it, but here is the sample

heq
  • 412
  • 2
  • 8
  • I've been attempting to use the global mouse hook - which is probably what I need to use... but thus far I can only get this to work for WH_MOUSE, which only captures messages for my app, where-as WH_MOUSE_LL is supposed to capture all messages - but I can't get the code to initiate the capture (it errors out). (It seems I'm in no-man's land and in a bit of unsupported functionality for C#). Thanks – ChronoFish Dec 03 '12 at 18:43
  • Also, I don't speak or read Russian so the forum link doesn't help me. – ChronoFish Dec 03 '12 at 18:48
  • There is no need to read Russian, this is a link on mouse hook sample with comments all in English. And it contain few things you need, for example: `private const int WH_KEYBOARD_LL = 13;` and `hMouseHook = SetWindowsHookEx( WH_MOUSE_LL, MouseHookProcedure, Marshal.GetHINSTANCE( Assembly.GetExecutingAssembly().GetModules()[0]), 0);` – heq Dec 04 '12 at 01:26
  • Just check a look. I added this link, cause I have it in my 'favorites' lol. There is no need to read something in Russian – heq Dec 04 '12 at 01:29
1

As others have stated this is at the very least troublesome in dot net. I would highly recommend you switch over to C++ / Win32 to achieve this. I think you will save yourself a lot of headache in the long run.

major-mann
  • 2,602
  • 22
  • 37
  • Yes you're right. This is the route I'm going ... but MS, in my opinion, has done such a poor job of the API that it makes programming in C++ infuriating. I'll get it done, kicking and screaming all the way to the end. In this day and age it should be trivial to take a string out of the registry and post it to a control - but you've got to jump through hoops to just get a "LPCTSTR" to print to "cout".... – ChronoFish Dec 13 '12 at 03:49
  • If you really want to avoid C++ you can try VB6 out. I cut my teeth on VB6 and Win32 :) As far as I recall it is quite effective at working with the win32 apis (But somewhat limited as a langauge). The other great thing is as its been around a while, and is still used a lot, there is a lot of good documentation out there as well. – major-mann Dec 14 '12 at 09:05
0

I don't think it is possible to prevent a mouse click via C# .Net. However, you might be able to lock the mouse pointer in one position via ClipCursor. The below link may help you with that.

If you essentially want to prevent one mouse from being used why not just disable the mouse like you could if you went to device manager and clicked "disable". This can be done programmatically as solved here.

Here is the code copied from the link above:

public static class DisableHardware
{
    const uint DIF_PROPERTYCHANGE = 0x12;
    const uint DICS_ENABLE = 1;
    const uint DICS_DISABLE = 2;  // disable device
    const uint DICS_FLAG_GLOBAL = 1; // not profile-specific
    const uint DIGCF_ALLCLASSES = 4;
    const uint DIGCF_PRESENT = 2;
    const uint ERROR_INVALID_DATA = 13;
    const uint ERROR_NO_MORE_ITEMS = 259;
    const uint ERROR_ELEMENT_NOT_FOUND = 1168;

    static DEVPROPKEY DEVPKEY_Device_DeviceDesc;
    static DEVPROPKEY DEVPKEY_Device_HardwareIds;

    [StructLayout(LayoutKind.Sequential)]
    struct SP_CLASSINSTALL_HEADER
    {
        public UInt32 cbSize;
        public UInt32 InstallFunction;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct SP_PROPCHANGE_PARAMS
    {
        public SP_CLASSINSTALL_HEADER ClassInstallHeader;
        public UInt32 StateChange;
        public UInt32 Scope;
        public UInt32 HwProfile;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct SP_DEVINFO_DATA
    {
        public UInt32 cbSize;
        public Guid classGuid;
        public UInt32 devInst;
        public IntPtr reserved;     // CHANGE #1 - was UInt32
    }

    [StructLayout(LayoutKind.Sequential)]
    struct DEVPROPKEY
    {
        public Guid fmtid;
        public UInt32 pid;
    }

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern IntPtr SetupDiGetClassDevsW(
        [In] ref Guid ClassGuid,
        [MarshalAs(UnmanagedType.LPWStr)]
string Enumerator,
        IntPtr parent,
        UInt32 flags);

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern bool SetupDiDestroyDeviceInfoList(IntPtr handle);

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet,
        UInt32 memberIndex,
        [Out] out SP_DEVINFO_DATA deviceInfoData);

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern bool SetupDiSetClassInstallParams(
        IntPtr deviceInfoSet,
        [In] ref SP_DEVINFO_DATA deviceInfoData,
        [In] ref SP_PROPCHANGE_PARAMS classInstallParams,
        UInt32 ClassInstallParamsSize);

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern bool SetupDiChangeState(
        IntPtr deviceInfoSet,
        [In] ref SP_DEVINFO_DATA deviceInfoData);

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern bool SetupDiGetDevicePropertyW(
            IntPtr deviceInfoSet,
            [In] ref SP_DEVINFO_DATA DeviceInfoData,
            [In] ref DEVPROPKEY propertyKey,
            [Out] out UInt32 propertyType,
            IntPtr propertyBuffer,
            UInt32 propertyBufferSize,
            out UInt32 requiredSize,
            UInt32 flags);

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern bool SetupDiGetDeviceRegistryPropertyW(
      IntPtr DeviceInfoSet,
      [In] ref SP_DEVINFO_DATA  DeviceInfoData,
      UInt32 Property,
      [Out] out UInt32  PropertyRegDataType,
      IntPtr PropertyBuffer,
      UInt32 PropertyBufferSize,
      [In,Out] ref UInt32 RequiredSize
    );

    static DisableHardware()
    {
        DisableHardware.DEVPKEY_Device_DeviceDesc = new DEVPROPKEY();
        DEVPKEY_Device_DeviceDesc.fmtid = new Guid(
                0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67,
                0xd1, 0x46, 0xa8, 0x50, 0xe0);
        DEVPKEY_Device_DeviceDesc.pid = 2;

        DEVPKEY_Device_HardwareIds = new DEVPROPKEY();
        DEVPKEY_Device_HardwareIds.fmtid = new Guid(
            0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67,
            0xd1, 0x46, 0xa8, 0x50, 0xe0);
        DEVPKEY_Device_HardwareIds.pid = 3;
    }




    public static void DisableDevice(Func<string, bool> filter, bool disable = true)
    {
        IntPtr info = IntPtr.Zero;
        Guid NullGuid = Guid.Empty;
        try
        {
            info = SetupDiGetClassDevsW(
                ref NullGuid,
                null,
                IntPtr.Zero,
                DIGCF_ALLCLASSES);
            CheckError("SetupDiGetClassDevs");

            SP_DEVINFO_DATA devdata = new SP_DEVINFO_DATA();
            devdata.cbSize = (UInt32)Marshal.SizeOf(devdata);

            // Get first device matching device criterion.
            for (uint i = 0; ; i++)
            {
                SetupDiEnumDeviceInfo(info,
                    i,
                    out devdata);
                // if no items match filter, throw
                if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
                    CheckError("No device found matching filter.", 0xcffff);
                CheckError("SetupDiEnumDeviceInfo");

                string devicepath = GetStringPropertyForDevice(info,
                                           devdata, 1); // SPDRP_HARDWAREID

                // Uncomment to print name/path
                //Console.WriteLine(GetStringPropertyForDevice(info,
                //                         devdata, DEVPKEY_Device_DeviceDesc));
                //Console.WriteLine("   {0}", devicepath);
                if (devicepath != null && filter(devicepath)) break;

            }

            SP_CLASSINSTALL_HEADER header = new SP_CLASSINSTALL_HEADER();
            header.cbSize = (UInt32)Marshal.SizeOf(header);
            header.InstallFunction = DIF_PROPERTYCHANGE;

            SP_PROPCHANGE_PARAMS propchangeparams = new SP_PROPCHANGE_PARAMS();
            propchangeparams.ClassInstallHeader = header;
            propchangeparams.StateChange = disable ? DICS_DISABLE : DICS_ENABLE;
            propchangeparams.Scope = DICS_FLAG_GLOBAL;
            propchangeparams.HwProfile = 0;

            SetupDiSetClassInstallParams(info,
                ref devdata,
                ref propchangeparams,
                (UInt32)Marshal.SizeOf(propchangeparams));
            CheckError("SetupDiSetClassInstallParams");

            SetupDiChangeState(
                info,
                ref devdata);
            CheckError("SetupDiChangeState");
        }
        finally
        {
            if (info != IntPtr.Zero)
                SetupDiDestroyDeviceInfoList(info);
        }
    }
    private static void CheckError(string message, int lasterror = -1)
    {

        int code = lasterror == -1 ? Marshal.GetLastWin32Error() : lasterror;
        if (code != 0)
            throw new ApplicationException(
                String.Format("Error disabling hardware device (Code {0}): {1}",
                    code, message));
    }

    private static string GetStringPropertyForDevice(IntPtr info, SP_DEVINFO_DATA devdata,
        uint propId)
    {
        uint proptype, outsize;
        IntPtr buffer = IntPtr.Zero;
        try
        {
            uint buflen = 512;
            buffer = Marshal.AllocHGlobal((int)buflen);
            outsize=0;
            // CHANGE #2 - Use this instead of SetupDiGetDeviceProperty 
            SetupDiGetDeviceRegistryPropertyW(
                info,
                ref devdata,
                propId,
                out proptype,
                buffer,
                buflen,
                ref outsize);
            byte[] lbuffer = new byte[outsize];
            Marshal.Copy(buffer, lbuffer, 0, (int)outsize);
            int errcode = Marshal.GetLastWin32Error();
            if (errcode == ERROR_INVALID_DATA) return null;
            CheckError("SetupDiGetDeviceProperty", errcode);
            return Encoding.Unicode.GetString(lbuffer);
        }
        finally
        {
            if (buffer != IntPtr.Zero)
                Marshal.FreeHGlobal(buffer);
        }
    }

}

http://msdn.microsoft.com/en-us/library/windows/desktop/ms648383%28v=vs.85%29.aspx http://www.pinvoke.net/default.aspx/user32/ClipCursor.html

Community
  • 1
  • 1
FrostyFire
  • 3,212
  • 3
  • 29
  • 53
  • Thanks for the suggestion but clipping is something I want to avoid. Thanks. – ChronoFish Dec 13 '12 at 03:44
  • @ChronoFish what about my other suggestion: Just disable the device programatically? – FrostyFire Dec 13 '12 at 03:50
  • I don't want the second mouse disabled. My application needs to know when the second mouse is "clicked" - but I don't want other applications to know when the second mouse is clicked. – ChronoFish Dec 13 '12 at 03:59