1

I've spent a while getting my mouse to be able to right-click in Windows XP. Here's the code:

public class Mouse
{
    [DllImport("user32.dll", SetLastError = true)]
    internal static extern uint SendInput(uint numberOfInputs, Input[] inputs, int sizeOfInputStructure);

    private const int RightDown = 0x0008;
    private const int RightUp = 0x0010;
    private const int InputMouse = 0;

    public void RightClick<T>(T element) where T: AutomationElementWrapper
    {

        var point = element.Element.GetClickablePoint();
        var processId = element.Element.GetCurrentPropertyValue(AutomationElement.ProcessIdProperty);
        var window = AutomationElement.RootElement.FindFirst(
            TreeScope.Children,
            new PropertyCondition(AutomationElement.ProcessIdProperty,
                                  processId));

        window.SetFocus();

         var x = (int)point.X;
         var y = (int)point.Y;

        System.Windows.Forms.Cursor.Position = new System.Drawing.Point(x, y);

        SendInput(2, new[] {InputFor(RightDown, x, y), InputFor(RightUp, x, y)}, Marshal.SizeOf(typeof (Input)));
    }


    private static Input InputFor(uint mouseButtonAction, int x, int y)
    {
        var input = new Input
                        {
                            Dx = x,
                            Dy = y,
                            MouseData = 0,
                            DwFlags = mouseButtonAction,
                            Time = 0,
                            DwType = InputMouse,
                            MouseExtraInfo = new IntPtr()
                        };
        return input;
    }

    internal struct Input
    {
        public int DwType;
        public int Dx;
        public int Dy;
        public uint MouseData;
        public uint DwFlags;
        public uint Time;
        public IntPtr MouseExtraInfo;
    }
}

This doesn't work in Windows 7. Why not, and how do I fix it?

For more information, I'm using this in an automation tool to bring up a context menu.

Edit: @GSerg's link looks helpful. I'm not sure what goes in the "type" field of the input once you've added the union, though - I left it blank, and now this causes my screensaver to come on. Ah, the joys of Win32. Any help appreciated.

Lunivore
  • 17,277
  • 4
  • 47
  • 92
  • 1
    Ah, the `INPUT` structure. [You're doing it wrong](http://blogs.msdn.com/b/oldnewthing/archive/2009/08/13/9867383.aspx). – GSerg Jan 15 '12 at 22:16
  • Care to illuminate what "right" would look like? It took me a while to put this together and I'm new to Win32. There's 25 points in it for you if you do... – Lunivore Jan 15 '12 at 22:18
  • Why, I do care :) Which is why there's a link in my comment that shows a correct `INPUT` pinvoke declaration and explains why. Other than that, I'm not entirely sure the issue is caused by the erroneous declaration (although if your Windows 7 machine is 64-bit, it certainly might be), so I made a comment instead of an answer. – GSerg Jan 15 '12 at 22:22
  • Hm, yes, my machine is 64-bit. Your link explains why for people who are already familiar with Win32. I managed to get it to make the screensaver come on! Thank you for the link anyway. I really need someone to help me fill in some of the blanks that are still on that page, though. – Lunivore Jan 15 '12 at 22:54
  • See [this answer](http://stackoverflow.com/a/8885228/660175) for an example that uses SendInput, hopefully you can just cut/paste the relevant definitions, even though it's doing keyboard vs mouse input. – BrendanMcK Jan 17 '12 at 12:27
  • Thanks, I did it with @GSerg's link though. I did it! It works! I'm very happy now. If you write an answer with that in I'll give you the points and everything. – Lunivore Jan 17 '12 at 21:10

1 Answers1

2

Here's my attempt at a solution - this test program combines the code from the opening question, the pinvoke.net SendInput page, and the oldnewthing 64-bit structure alignment workaround linked to by GSerg.

With this, right-clicking works for me on Win7 x64:

using System;
using System.Runtime.InteropServices;
using System.Windows.Automation;
using WiPFlash;
using WiPFlash.Components;
using WiPFlash.Framework;
using WiPFlash.Util;
using WiPFlash.Exceptions;

using NUnit.Framework;

namespace MouseRightClick
{
    class Program
    {
        static void Main(string[] args)
        {
            Application application = new ApplicationLauncher(TimeSpan.Parse("00:00:20"))
                .LaunchOrRecycle("foo", @"C:\\hg\\wipflash\\Example.PetShop\\bin\\Debug\\Example.PetShop.exe", Assert.Fail);

            var window = application.FindWindow("petShopWindow");
            var totalLabel = window.Find<Label>("copyPetContextTarget");
            Mouse mouse = new Mouse();
            mouse.RightClick(totalLabel);
        }
    }

    public class Mouse
    {
        [DllImport("user32.dll", SetLastError = true)]
        static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputStructure);

        private const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
        private const int MOUSEEVENTF_RIGHTUP = 0x0010;
        private const int INPUT_MOUSE = 0;
        private const int INPUT_KEYBOARD = 1;
        private const int INPUT_HARDWARE = 2;

        public void RightClick<T>(T element) where T : AutomationElementWrapper
        {
            var point = element.Element.GetClickablePoint();
            var processId = element.Element.GetCurrentPropertyValue(AutomationElement.ProcessIdProperty);
            var window = AutomationElement.RootElement.FindFirst(
                TreeScope.Children,
                new PropertyCondition(AutomationElement.ProcessIdProperty,
                                      processId));

            window.SetFocus();

            var x = (int)point.X;
            var y = (int)point.Y;

            System.Windows.Forms.Cursor.Position = new System.Drawing.Point(x, y);

            SendInput(2, new[] {
                InputFor(MOUSEEVENTF_RIGHTDOWN, x, y),
                InputFor(MOUSEEVENTF_RIGHTUP, x, y) },
                Marshal.SizeOf(typeof(INPUT)));
        }


        private static INPUT InputFor(uint mouseButtonAction, int x, int y)
        {
            var input = new INPUT();
            input.type = INPUT_MOUSE;
            input.u.mi.dwFlags = mouseButtonAction;
            input.u.mi.time = 0;
            input.u.mi.dwExtraInfo = IntPtr.Zero;            
            return input;
        }

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

        [StructLayout(LayoutKind.Sequential)]
        internal struct KEYBDINPUT
        {
            public ushort wVk;
            public ushort wScan;
            public uint dwFlags;
            public uint time;
            public IntPtr dwExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct HARDWAREINPUT
        {
            public uint uMsg;
            public ushort wParamL;
            public ushort wParamH;
        }

        [StructLayout(LayoutKind.Explicit)]
        internal struct INPUT_UNION
        {
            [FieldOffset(0)]
            public MOUSEINPUT mi;
            [FieldOffset(0)]
            public KEYBDINPUT ki;
            [FieldOffset(0)]
            public HARDWAREINPUT hi;
        };

        [StructLayout(LayoutKind.Sequential)]
        internal struct INPUT
        {
            public int type;
            public INPUT_UNION u;
        }
    }

}
Bill Agee
  • 3,606
  • 20
  • 17
  • Oh, wow... and you're actually using WiPFlash to do it too! I've solved the problem, exactly this way, so hopefully this will help anyone else who has the same issue. Thank you for writing it up! The next WiPFlash release will now support context menus in 64-bit too! – Lunivore Jan 19 '12 at 17:56